# Reusable Gitea Actions workflow — the Gitea-flavoured equivalent # of /kibot-ci.yml. Same fab outputs, same Astable upload contract. # # Both files live in the same kicad-ci repo on the same branch; they # don't collide because GitLab CI looks at /kibot-ci.yml and Gitea # Actions looks at /.gitea/workflows/*.yml. Maintain in lockstep — # any change that affects the upload contract should land in both. # # How a PCB project on Gitea uses this: # # # In the PCB project's .gitea/workflows/ci.yml # name: KiCad CI # on: # push: # branches: [dev, main] # pull_request: # branches: [dev, main] # jobs: # kicad: # uses: Micromelon/kicad-ci/.gitea/workflows/kibot.yml@main # with: # project_name: m100301-dengus_cab_tablet_mainboard # secrets: # ASTABLE_URL: ${{ secrets.ASTABLE_URL }} # ASTABLE_API_TOKEN: ${{ secrets.ASTABLE_API_TOKEN }} # # Gitea Actions secrets are configured under Repo → Settings → Actions # → Secrets (or at the org level for inheritance, same idea as # GitLab's group-level CI variables). # # Required secrets: # ASTABLE_URL e.g. https://astable.timhadwen.com # ASTABLE_API_TOKEN minted at /manage/api-tokens (ast_…) name: KiBot CI (reusable) on: workflow_call: inputs: project_name: required: true type: string description: "Project directory name — also used as the partIpn for Astable" kibot_image: required: false type: string default: "ghcr.io/inti-cmnb/kicad9_auto:1.8.4" secrets: ASTABLE_URL: required: true ASTABLE_API_TOKEN: required: true env: PROJECT_NAME: ${{ inputs.project_name }} jobs: # ─── Preflight ────────────────────────────────────────────────────── extract_version: runs-on: ubuntu-latest outputs: version: ${{ steps.derive.outputs.version }} steps: - uses: actions/checkout@v4 - id: derive env: # Inject via env so commit-message / PR-title backticks or # other shell metacharacters don't trigger command substitution # when bash evaluates the script. Direct ${{ … }} interpolation # into the run: body would splice the raw text into bash — # backticks become $(…). Keep this env-var indirection. COMMIT_MESSAGE: ${{ github.event.head_commit.message }} PR_TITLE: ${{ github.event.pull_request.title }} GIT_SHA: ${{ github.sha }} run: | # Dev branch: VERSION=dev. Main branch: parse VX.Y from commit # message (matches the GitLab-side rule). MR-to-main uses the # MR title's V-token, falling back to dev. if [ "${{ github.ref }}" = "refs/heads/main" ]; then VERSION=$(printf '%s' "$COMMIT_MESSAGE" | grep -oE 'V[0-9]+(\.[0-9]+)*' | head -1 || true) [ -z "$VERSION" ] && VERSION="${GIT_SHA:0:8}" elif [ "${{ github.event_name }}" = "pull_request" ]; then VERSION=$(printf '%s' "$PR_TITLE" | grep -oE 'V[0-9]+(\.[0-9]+)*' | head -1 || true) [ -z "$VERSION" ] && VERSION="dev" else VERSION="dev" fi echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "Resolved VERSION=$VERSION" run_erc: runs-on: ubuntu-latest needs: extract_version container: image: ${{ inputs.kibot_image }} steps: - uses: actions/checkout@v4 with: submodules: recursive - run: | if [ -f "${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch" ]; then cat > /tmp/erc.kibot.yaml << 'EOF' kibot: version: 1 preflight: erc: enabled: true warnings_as_errors: false EOF kibot -e "${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch" \ -c /tmp/erc.kibot.yaml \ -d Validation/ fi - uses: actions/upload-artifact@v4 if: always() with: name: Validation path: Validation/ retention-days: 7 run_drc: runs-on: ubuntu-latest needs: [extract_version, run_erc] container: image: ${{ inputs.kibot_image }} env: VERSION: ${{ needs.extract_version.outputs.version }} steps: - uses: actions/checkout@v4 with: submodules: recursive - uses: actions/download-artifact@v4 with: name: Validation path: Validation/ - run: | if [ -f "${PROJECT_NAME}/${PROJECT_NAME}.kicad_pcb" ]; then PART_NUM=$(echo "${PROJECT_NAME}" | cut -d"-" -f1) cat > /tmp/drc.kibot.yaml << EOF kibot: version: 1 preflight: set_text_variables: - name: 'name' text: '${PART_NUM}' - name: 'rev' text: '${VERSION}' - name: 'rev_pcb' text: '${VERSION}' drc: enabled: true warnings_as_errors: false EOF kibot -e "${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch" \ -b "${PROJECT_NAME}/${PROJECT_NAME}.kicad_pcb" \ -c /tmp/drc.kibot.yaml \ -d Validation/ fi - uses: actions/upload-artifact@v4 if: always() with: name: Validation path: Validation/ overwrite: true retention-days: 7 # ─── Fabrication ──────────────────────────────────────────────────── # Reusable step block via a small composite isn't worth it — each # generate_* job is small enough to inline its own checkout + # kibot invocation. Common KIBOT_VAR_* env is set per-job. generate_schematic: runs-on: ubuntu-latest needs: [extract_version, run_erc, run_drc] container: image: ${{ inputs.kibot_image }} env: VERSION: ${{ needs.extract_version.outputs.version }} KIBOT_VAR_rev: ${{ needs.extract_version.outputs.version }} KIBOT_VAR_rev_pcb: ${{ needs.extract_version.outputs.version }} steps: - uses: actions/checkout@v4 with: { submodules: recursive } - run: | export KIBOT_VAR_name=$(echo "${PROJECT_NAME}" | cut -d"-" -f1) if [ -f "${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch" ]; then kibot -e "${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch" \ -c "$GITHUB_WORKSPACE/.gitlab/configs/sch.kibot.yaml" \ -d "$GITHUB_WORKSPACE/Fabrication/${PROJECT_NAME}" mv "Fabrication/${PROJECT_NAME}"/*.pdf Fabrication/ 2>/dev/null || true fi - uses: actions/upload-artifact@v4 with: name: Fabrication path: Fabrication/ retention-days: 7 generate_bom: runs-on: ubuntu-latest needs: [extract_version, run_erc, run_drc] container: image: ${{ inputs.kibot_image }} env: VERSION: ${{ needs.extract_version.outputs.version }} KIBOT_VAR_rev: ${{ needs.extract_version.outputs.version }} KIBOT_VAR_rev_pcb: ${{ needs.extract_version.outputs.version }} steps: - uses: actions/checkout@v4 with: { submodules: recursive } - run: | export KIBOT_VAR_name=$(echo "${PROJECT_NAME}" | cut -d"-" -f1) if [ -f "${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch" ]; then kibot -e "${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch" \ -c "$GITHUB_WORKSPACE/.gitlab/configs/sch.kibot.yaml" \ -d "$GITHUB_WORKSPACE/Fabrication/${PROJECT_NAME}" mv "Fabrication/${PROJECT_NAME}"/*.csv Fabrication/ 2>/dev/null || true mv "Fabrication/${PROJECT_NAME}"/*.xlsx Fabrication/ 2>/dev/null || true fi - uses: actions/upload-artifact@v4 with: name: Fabrication-bom path: Fabrication/ retention-days: 7 generate_3d: runs-on: ubuntu-latest needs: [extract_version, run_erc, run_drc] container: image: ${{ inputs.kibot_image }} env: VERSION: ${{ needs.extract_version.outputs.version }} KIBOT_VAR_rev: ${{ needs.extract_version.outputs.version }} KIBOT_VAR_rev_pcb: ${{ needs.extract_version.outputs.version }} steps: - uses: actions/checkout@v4 with: { submodules: recursive } - run: | export KIBOT_VAR_name=$(echo "${PROJECT_NAME}" | cut -d"-" -f1) if [ -f "${PROJECT_NAME}/${PROJECT_NAME}.kicad_pcb" ]; then kibot -e "${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch" \ -c "$GITHUB_WORKSPACE/.gitlab/configs/mech.kibot.yaml" \ -d "$GITHUB_WORKSPACE/Fabrication/${PROJECT_NAME}" mv "Fabrication/${PROJECT_NAME}"/*.step Fabrication/ 2>/dev/null || true fi - uses: actions/upload-artifact@v4 with: name: Fabrication-3d path: Fabrication/ retention-days: 7 # Main-only jobs — gated by the `if:` so dev branches skip them. generate_gerbers: if: github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.base_ref == 'main') runs-on: ubuntu-latest needs: [extract_version, run_erc, run_drc] container: image: ${{ inputs.kibot_image }} env: VERSION: ${{ needs.extract_version.outputs.version }} KIBOT_VAR_rev: ${{ needs.extract_version.outputs.version }} KIBOT_VAR_rev_pcb: ${{ needs.extract_version.outputs.version }} steps: - uses: actions/checkout@v4 with: { submodules: recursive } - run: | export KIBOT_VAR_name=$(echo "${PROJECT_NAME}" | cut -d"-" -f1) if [ -f "${PROJECT_NAME}/${PROJECT_NAME}.kicad_pcb" ]; then kibot -e "${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch" \ -c "$GITHUB_WORKSPACE/.gitlab/configs/pcb_main.kibot.yaml" \ -d "$GITHUB_WORKSPACE/Fabrication/${PROJECT_NAME}" mv "Fabrication/${PROJECT_NAME}"/*.zip Fabrication/ 2>/dev/null || true fi - uses: actions/upload-artifact@v4 with: name: Fabrication-gerbers path: Fabrication/ retention-days: 7 generate_position: if: github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.base_ref == 'main') runs-on: ubuntu-latest needs: [extract_version, run_erc, run_drc] container: image: ${{ inputs.kibot_image }} env: VERSION: ${{ needs.extract_version.outputs.version }} KIBOT_VAR_rev: ${{ needs.extract_version.outputs.version }} KIBOT_VAR_rev_pcb: ${{ needs.extract_version.outputs.version }} steps: - uses: actions/checkout@v4 with: { submodules: recursive } - run: | export KIBOT_VAR_name=$(echo "${PROJECT_NAME}" | cut -d"-" -f1) if [ -f "${PROJECT_NAME}/${PROJECT_NAME}.kicad_pcb" ]; then kibot -e "${PROJECT_NAME}/${PROJECT_NAME}.kicad_sch" \ -c "$GITHUB_WORKSPACE/.gitlab/configs/pos.kibot.yaml" \ -d "$GITHUB_WORKSPACE/Fabrication/${PROJECT_NAME}" mv "Fabrication/${PROJECT_NAME}"/*cpl*.csv Fabrication/ 2>/dev/null || true fi - uses: actions/upload-artifact@v4 with: name: Fabrication-position path: Fabrication/ retention-days: 7 generate_panel: if: github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.base_ref == 'main') runs-on: ubuntu-latest needs: [extract_version, run_erc, run_drc] container: image: ${{ inputs.kibot_image }} continue-on-error: true # optional per the GitLab side env: VERSION: ${{ needs.extract_version.outputs.version }} KIBOT_VAR_rev: ${{ needs.extract_version.outputs.version }} KIBOT_VAR_rev_pcb: ${{ needs.extract_version.outputs.version }} steps: - uses: actions/checkout@v4 with: { submodules: recursive } - run: | export KIBOT_VAR_name=$(echo "${PROJECT_NAME}" | cut -d"-" -f1) PANEL_JSON=$(find "${PROJECT_NAME}" -name "*_panel.json" 2>/dev/null | head -1) if [ -n "$PANEL_JSON" ] && [ -f "$PANEL_JSON" ]; then PANEL_NAME=$(basename "${PANEL_JSON%.json}") mkdir -p "panels/${PANEL_NAME}" PCB_FILE="${PROJECT_NAME}/${PROJECT_NAME}.kicad_pcb" [ -f ".gitlab/.scripts/pre_panel.py" ] && python3 .gitlab/.scripts/pre_panel.py "$PCB_FILE" "$PANEL_JSON" kikit panelize -p "$PANEL_JSON" "$PCB_FILE" "panels/${PANEL_NAME}/${PANEL_NAME}.kicad_pcb" cp .gitlab/templates/micromelon_default/micromelon_default.kicad_sch "panels/${PANEL_NAME}/${PANEL_NAME}.kicad_sch" 2>/dev/null || true cp "${PROJECT_NAME}/fp-lib-table" "panels/${PANEL_NAME}/" 2>/dev/null || true cp -r "${PROJECT_NAME}/libs" "panels/${PANEL_NAME}/" 2>/dev/null || true [ -f ".gitlab/.scripts/post_panel.py" ] && python3 .gitlab/.scripts/post_panel.py "panels/${PANEL_NAME}/${PANEL_NAME}.kicad_pro" "$PCB_FILE" cd panels kibot -e "${PANEL_NAME}/${PANEL_NAME}.kicad_sch" \ -c "$GITHUB_WORKSPACE/.gitlab/configs/panel.kibot.yaml" \ -d "$GITHUB_WORKSPACE/Fabrication/panels" cd "$GITHUB_WORKSPACE" mv Fabrication/panels/*.zip Fabrication/ 2>/dev/null || true else echo "No panel config — skipping" fi - uses: actions/upload-artifact@v4 with: name: Fabrication-panel path: Fabrication/ retention-days: 7 # ─── Astable upload ───────────────────────────────────────────────── # Mirrors astable_dev / astable_main on the GitLab side. The fab # artifacts above are downloaded into a shared Fabrication/ tree; # upload-bom.sh handles the POST + polling. astable_dev: if: github.ref == 'refs/heads/dev' || (github.event_name == 'pull_request' && github.base_ref == 'dev') runs-on: ubuntu-latest needs: [extract_version, generate_schematic, generate_bom, generate_3d] container: image: alpine:3.20 env: VERSION: ${{ needs.extract_version.outputs.version }} ASTABLE_URL: ${{ secrets.ASTABLE_URL }} ASTABLE_API_TOKEN: ${{ secrets.ASTABLE_API_TOKEN }} steps: - uses: actions/checkout@v4 with: { submodules: recursive } - run: apk add --no-cache bash curl jq # Pull each fab job's artifact into a unified Fabrication/ tree. - uses: actions/download-artifact@v4 with: { name: Fabrication, path: Fabrication/ } - uses: actions/download-artifact@v4 with: { name: Fabrication-bom, path: Fabrication/ } - uses: actions/download-artifact@v4 with: { name: Fabrication-3d, path: Fabrication/ } - name: Upload to Astable (dev rev) run: | install -m 0755 "$GITHUB_WORKSPACE/.gitlab/scripts/upload-bom.sh" /usr/local/bin/upload-bom.sh bash -s <<'BASH_SCRIPT' set -euo pipefail FAB="$GITHUB_WORKSPACE/Fabrication" SUB="$FAB/${PROJECT_NAME}" VAL="$GITHUB_WORKSPACE/Validation" ARGS=( --file "$FAB/${PROJECT_NAME}_dev_bom.csv" --part-ipn "$PROJECT_NAME" --branch dev --commit-sha "$GITHUB_SHA" --commit-message "${COMMIT_MESSAGE:-}" ) [ -f "$FAB/${PROJECT_NAME}_dev_cpl.csv" ] && ARGS+=(--cpl "$FAB/${PROJECT_NAME}_dev_cpl.csv") [ -f "$FAB/${PROJECT_NAME}_dev.step" ] && ARGS+=(--step "$FAB/${PROJECT_NAME}_dev.step") [ -f "$FAB/${PROJECT_NAME}_dev_schematic.pdf" ] && ARGS+=(--schematic-pdf "$FAB/${PROJECT_NAME}_dev_schematic.pdf") [ -f "$SUB/${PROJECT_NAME}_dev_ibom.html" ] && ARGS+=(--ibom-html "$SUB/${PROJECT_NAME}_dev_ibom.html") [ -f "$SUB/${PROJECT_NAME}_dev_bom.xlsx" ] && ARGS+=(--bom-xlsx "$SUB/${PROJECT_NAME}_dev_bom.xlsx") [ -f "$SUB/PCBA_${PROJECT_NAME}_dev.png" ] && ARGS+=(--pcba-image "$SUB/PCBA_${PROJECT_NAME}_dev.png") [ -f "$SUB/PCB_${PROJECT_NAME}_dev.png" ] && ARGS+=(--pcb-image "$SUB/PCB_${PROJECT_NAME}_dev.png") [ -f "$VAL/${PROJECT_NAME}-erc.html" ] && ARGS+=(--erc-report "$VAL/${PROJECT_NAME}-erc.html") [ -f "$VAL/${PROJECT_NAME}-drc.html" ] && ARGS+=(--drc-report "$VAL/${PROJECT_NAME}-drc.html") upload-bom.sh "${ARGS[@]}" BASH_SCRIPT env: COMMIT_MESSAGE: ${{ github.event.head_commit.message }} astable_main: if: github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.base_ref == 'main') runs-on: ubuntu-latest needs: - extract_version - generate_schematic - generate_bom - generate_3d - generate_gerbers - generate_position - generate_panel container: image: alpine:3.20 env: VERSION: ${{ needs.extract_version.outputs.version }} ASTABLE_URL: ${{ secrets.ASTABLE_URL }} ASTABLE_API_TOKEN: ${{ secrets.ASTABLE_API_TOKEN }} steps: - uses: actions/checkout@v4 with: { submodules: recursive } - run: apk add --no-cache bash curl jq - uses: actions/download-artifact@v4 with: { name: Fabrication, path: Fabrication/ } - uses: actions/download-artifact@v4 with: { name: Fabrication-bom, path: Fabrication/ } - uses: actions/download-artifact@v4 with: { name: Fabrication-3d, path: Fabrication/ } - uses: actions/download-artifact@v4 with: { name: Fabrication-gerbers, path: Fabrication/ } - uses: actions/download-artifact@v4 with: { name: Fabrication-position, path: Fabrication/ } - uses: actions/download-artifact@v4 with: { name: Fabrication-panel, path: Fabrication/ } continue-on-error: true - name: Upload to Astable (main rev) run: | install -m 0755 "$GITHUB_WORKSPACE/.gitlab/scripts/upload-bom.sh" /usr/local/bin/upload-bom.sh bash -s <<'BASH_SCRIPT' set -euo pipefail FAB="$GITHUB_WORKSPACE/Fabrication" SUB="$FAB/${PROJECT_NAME}" VAL="$GITHUB_WORKSPACE/Validation" # MR-to-main → upload to dev rev (pre-ordering window). # Actual main pushes → released rev. if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then BRANCH=dev else BRANCH=main fi ARGS=( --file "$FAB/${PROJECT_NAME}_${VERSION}_bom.csv" --part-ipn "$PROJECT_NAME" --branch "$BRANCH" --commit-sha "$GITHUB_SHA" --commit-message "${COMMIT_MESSAGE:-}" ) [ -f "$FAB/${PROJECT_NAME}_${VERSION}_cpl.csv" ] && ARGS+=(--cpl "$FAB/${PROJECT_NAME}_${VERSION}_cpl.csv") [ -f "$FAB/${PROJECT_NAME}_${VERSION}_JLC.zip" ] && ARGS+=(--gerbers "$FAB/${PROJECT_NAME}_${VERSION}_JLC.zip") [ -f "$FAB/${PROJECT_NAME}_panel_${VERSION}_JLC.zip" ] && ARGS+=(--panel-gerbers "$FAB/${PROJECT_NAME}_panel_${VERSION}_JLC.zip") [ -f "$FAB/${PROJECT_NAME}_${VERSION}.step" ] && ARGS+=(--step "$FAB/${PROJECT_NAME}_${VERSION}.step") [ -f "$FAB/${PROJECT_NAME}_${VERSION}_schematic.pdf" ] && ARGS+=(--schematic-pdf "$FAB/${PROJECT_NAME}_${VERSION}_schematic.pdf") [ -f "$SUB/${PROJECT_NAME}_${VERSION}_PCB.pdf" ] && ARGS+=(--pcb-pdf "$SUB/${PROJECT_NAME}_${VERSION}_PCB.pdf") [ -f "$SUB/${PROJECT_NAME}_${VERSION}_ibom.html" ] && ARGS+=(--ibom-html "$SUB/${PROJECT_NAME}_${VERSION}_ibom.html") [ -f "$SUB/${PROJECT_NAME}_${VERSION}_bom.xlsx" ] && ARGS+=(--bom-xlsx "$SUB/${PROJECT_NAME}_${VERSION}_bom.xlsx") [ -f "$SUB/PCBA_${PROJECT_NAME}_${VERSION}.png" ] && ARGS+=(--pcba-image "$SUB/PCBA_${PROJECT_NAME}_${VERSION}.png") [ -f "$SUB/PCB_${PROJECT_NAME}_${VERSION}.png" ] && ARGS+=(--pcb-image "$SUB/PCB_${PROJECT_NAME}_${VERSION}.png") [ -f "$VAL/${PROJECT_NAME}-erc.html" ] && ARGS+=(--erc-report "$VAL/${PROJECT_NAME}-erc.html") [ -f "$VAL/${PROJECT_NAME}-drc.html" ] && ARGS+=(--drc-report "$VAL/${PROJECT_NAME}-drc.html") upload-bom.sh "${ARGS[@]}" BASH_SCRIPT env: COMMIT_MESSAGE: ${{ github.event.head_commit.message }} - uses: actions/upload-artifact@v4 if: always() with: name: Fabrication-release path: Fabrication/ retention-days: 7