Automating KiCAD with KiBot and Gitea Actions

2025-07-04 Β· 3002 words Β· 15 minute read

KiCAD is my favourite PCB design program. I use it for Hobby projects as well as in my day job. I even wrote a rather successful plugin called kicad-jlcpcb-tools for it that lets the user select parts from the JLCPCB catalog and assign them to the footprints. Furthermore it generates the fabrication data consisting of a zip with all Gerber files and the Excellon drill file as well as a BOM CSV file and a CPL CSV file. These three files can be uploaded to JLCPCBs website to get an instant quote for the PCB including assembly.

I’m quite happy with this plugin but lately I don’t have enough spare time to keep up with all the upcoming issues and feature requests. What also bothers me is the fact that JLCPCB is a Chinese company. The do great work and deliver PCBs very fast, but especially for my day job, I need a reliable source for the PCBs. With the fast changing political things that happen around the world nowadays, I don’t feel comfortable that my company is reliant on a supplier from China. Also I want to strengthen European companies (I’m aware that still a considerable portion is still coming from China).

One more thin is that I use git to track my changes to the PCB projects. Wouldn’t it be awesome if we could create fabrication data automatically?

A few weeks ago, I heard a talk at Cosin by @seto . He showed how he uses the fairly new CLI of KiCAD to automate his PCB fabrication data generation.

I tried this in the past but never had it to a point where I could use it reliably, also I used KiBot for this instead of “just” the CLI. KiBot is an amazing project with a ton of features. It lets you generate various outputs from the project files such as schematics as PDF, BOM files as CSV, Gerber and Excellon files, images and 3D renderings and many more.

So I thought I might give this another try and came up with a fairly elegant solution, at least for what I need.

Project setup πŸ”—

Disclaimer: I use Gitea at the moment, but switching to Forgejo is on my ToDo list πŸ˜…

The following things should work with Gitea, Forgejo and Github and possibly more but I’ve just tested Gitea so far.

metadata.env πŸ”—

In my project I create a metadata.env file that looks like this:

1LONG_NAME=Smart-Meter-Interface
2SHORT_NAME=SMI
3VERSION=V1.0
4AUTHOR=Bouni
5KIBOT_CONFIG=.kibot/kibot-double-sided.yaml
6PCB_FILE=hardware/smart-meter-interface.kicad_pcb
7SCH_FILE=hardware/smart-meter-interface.kicad_sch
  • LONG_NAME is the full name of the project
  • SHORT_NAME is a short abbreviation
  • VERSION is the version of the project
  • KIBOT_CONFIG is either -double- or -single- depending if your project needs images of both sides.
  • PCB_FILE is the relative path to the .kicad_pcb file
  • SCH_FILE is the relative path to the .kicad_sch file

kibot configs πŸ”—

In a subfolder called .kibot I have two KiBot configs, one for single sided projects called kibot-single-sided.yaml and one for double sided projects called kibot-double-sided.yaml

I show the double sided version as the single sided one has just a few outputs less, apart from that they are identical

  1---
  2kibot:
  3  version: 1
  4
  5global:
  6  output: "@LONG_NAME@-%r-%i.%x"
  7  field_lcsc_part: LCSC  # this specifies the field name in which the LCSC number of a component is stored, for example C12345
  8
  9import:  # With this we import the template that generates all production files for JLCPCB
 10  - file: JLCPCB
 11    definitions:
 12      _KIBOT_POS_ONLY_SMD: false  # by default all THT components are omited, but I want to have them assembled as well
 13
 14outputs:
 15
 16  - name: 'Gerber'  # gerber files for my European manufacturer
 17    comment: 'Gerber files'
 18    type: 'gerber'
 19    dir: 'gerber'
 20    layers:
 21      - "F.Cu"
 22      - "B.Cu"
 23      - "F.Mask"
 24      - "B.Mask"
 25      - "F.Silkscreen"
 26      - "B.Silkscreen"
 27      - "Edge.Cuts"
 28    options:
 29      output: "@LONG_NAME@-%r-Gerber-%i.%x"
 30
 31  - name: 'Excellon'  # excellon files for my European manufacturer
 32    comment: 'Excellon drill files'
 33    type: 'excellon'
 34    dir: 'excellon'
 35    options:
 36      output: "@LONG_NAME@-%r-Drill.%x"
 37
 38  - name: 'Pick and Place'  # Pick n Place files for my European manufacturer
 39    comment: "Pick and Place file"
 40    type: 'position'
 41    dir: 'pick-and-place'
 42    options:
 43      output: "@LONG_NAME@-%r-PickAndPlace.%x"
 44      format: "CSV"
 45      separate_files_for_front_and_back: false
 46
 47  - name: 'BOM'  # Bill of material for my European manufacturer
 48    comment: "Bill of materials (XLSX)"
 49    type: bom
 50    options:
 51      output: "@LONG_NAME@-%r-StΓΌckliste.%x"
 52      format: "XLSX"
 53      columns:
 54        - "Row"
 55        - "References"
 56        - "Value"
 57        - "Footprint"
 58        - "Quantity per PCB"
 59        - "MPN"
 60        - "Manufacturer_Name"
 61      normalize_values: true
 62      xlsx:
 63        logo: false
 64        title: "StΓΌckliste @LONG_NAME@ %r"
 65        hide_pcb_info: true
 66        hide_stats_info: true
 67highlight_empty: false
 68
 69  - name: 'Schematic'  # schematic for my European manufacturer
 70    comment: "Print schematic (PDF)"
 71    type: pdf_sch_print
 72    options:
 73      output: "@LONG_NAME@-%r-Schema.%x"
 74
 75  - name: 'PCB TOP image'  # top image of the PCB without components for my European manufacturer
 76    comment: "PNG of the top side of the PCB"
 77    type: pcbdraw
 78    options:
 79      output: "@LONG_NAME@-%r-Top.%x"
 80      dpi: 1200
 81      format: png
 82
 83  - name: 'PCB BOTTOM image'  # bottom image of the PCB without components for my European manufacturer
 84    comment: "PNG of the bottom side of the PCB"
 85    type: pcbdraw
 86    options:
 87      output: "@LONG_NAME@-%r-Bottom.%x"
 88      dpi: 1200
 89      format: png
 90      bottom: true
 91
 92  - name: "3D Blender top image"  # 3D rendered image of the top for the README
 93    comment: "@LONG_NAME@ 3D render from top (High Quality)"
 94    type: blender_export
 95    options:
 96      render_options:
 97        background1: "#8FBCF7FF"
 98        background2: "#0B1E35FF"  
 99        samples: 20
100        resolution_y: 1440
101        resolution_x: 2560
102      point_of_view:
103        rotate_x: -5
104      outputs:
105        - type: render
106          output: "@LONG_NAME@-%r-Top-3D.png"
107
108  - name: "3D Blender bootom image"  # 3D rendered image of the bottom for the README
109    comment: "@LONG_NAME@ 3D render from bottom (High Quality)"
110    type: blender_export
111    options:
112      render_options:
113        background1: "#8FBCF7FF"
114        background2: "#0B1E35FF"  
115        samples: 20
116        resolution_y: 1440
117        resolution_x: 2560
118      point_of_view:
119        rotate_x: 5
120        rotate_y: 180
121      outputs:
122        - type: render
123          output: "@LONG_NAME@-%r-Bottom-3D.png"

Gitea action πŸ”—

Note: this can be a Github action or a Forgejo action as well, that just requires a changed folder name as far a s I can tell.

1name: Auto Release
2run-name: Auto Release Gestartet von ${{ gitea.actor }} πŸš€
3on:
4  push:
5    branches: [main]
6    paths: [metadata.env]

This part controls when the action should run, I decided to only run it if I change the metadata.env, usually the only thing that changes is the version.

1env:
2  BASE_URL: https://gitea.bouni.de

Here I set the base URL of my gitea instance, maybe there is a smarter way to achieve this but it works πŸ€·β€β™‚οΈ

jobs:
  release:
    runs-on: ubuntu-latest
    outputs:
      RELEASE_ID: ${{ steps.create_release.outputs.RELEASE_ID }}

This part is important, we need to pass the release id from one job to the other. For whatever reason the Gitea API uses a release id for the upload of files rather than the release tag πŸ™„

 1    steps:
 2      - name: Checkout code
 3        uses: actions/checkout@v3
 4        with:
 5          token: ${{ secrets.GITEA_TOKEN }}
 6          fetch-depth: 0 # Needed for tagging
 7      - name: Load metadata from metadata.env file
 8        id: metadata
 9        run: |
10          set -a
11          source metadata.env
12          set +a
13          echo "LONG_NAME=$LONG_NAME" >> $GITHUB_OUTPUT
14          echo "SHORT_NAME=$SHORT_NAME" >> $GITHUB_OUTPUT
15          echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
16          echo "AUTHOR=$AUTHOR" >> $GITHUB_OUTPUT

Checkout the repo, read the metadata.env into env vars and write the ones we need into $GITHUB_OUTPUT

1      - name: Install jq
2        id: install_jq
3        run: |
4          if ! command -v jq &> /dev/null; then
5            apt-get update && apt-get install -y jq
6          fi

Install jq , we need that later to parse JSON data

 1      - name: Detect release type
 2        id: version
 3        run: |
 4          # Check if it's a pre-release (contains RC, alpha, beta, dev, etc.)
 5          if [[ "${{ steps.metadata.outputs.VERSION }}" =~ -RC[0-9]*$ ]] || \
 6             [[ "${{ steps.metadata.outputs.VERSION }}" =~ -alpha[0-9]*$ ]] || \
 7             [[ "${{ steps.metadata.outputs.VERSION }}" =~ -beta[0-9]*$ ]] || \
 8             [[ "${{ steps.metadata.outputs.VERSION }}" =~ -dev[0-9]*$ ]] || \
 9             [[ "${{ steps.metadata.outputs.VERSION }}" =~ -pre[0-9]*$ ]]; then
10            echo "PRERELEASE=true" >> $GITHUB_OUTPUT
11            echo "Detected PRE-RELEASE: ${{ steps.metadata.outputs.VERSION }}"
12          else
13            echo "PRERELEASE=false" >> $GITHUB_OUTPUT
14            echo "Detected STABLE RELEASE: ${{ steps.metadata.outputs.VERSION }}"
15          fi
16      - name: Check if tag exists
17        id: check_tag
18        run: |
19          if git rev-parse "${{ steps.metadata.outputs.VERSION }}" >/dev/null 2>&1; then
20            echo "exists=true" >> $GITHUB_OUTPUT
21            echo "Tag ${{ steps.metadata.outputs.VERSION  }} already exists!"
22            exit 1
23          else
24            echo "exists=false" >> $GITHUB_OUTPUT
25          fi

Check if we have a Pre-Release and set a variable if so, then verify that this release does not exist. If it exists, we exit the action.

 1      - name: Update README
 2        if: steps.check_tag.outputs.exists == 'false'
 3        run: |
 4          # Replace version in README
 5          sed -i -E 's|V[0-9]+\.[0-9]+(-RC[0-9]+)?|${{ steps.metadata.outputs.VERSION  }}|g' README.md
 6
 7          # Delete pre-release warning
 8          sed -i '/This is a pre-release version/d' README.md
 9
10          # Add pre-release badge if it's a pre-release
11          if [[ "${{ steps.version.outputs.PRERELEASE }}" == "true" ]]; then
12            # Add pre-release warning to README
13            sed -i '1i\> **⚠️ This is a pre-release version. Use with caution!**' README.md
14          fi

Update the Versions in the README and add a pre-release warning at the top if it is a pre-release.

 1      - name: Commit README changes
 2        if: steps.check_tag.outputs.exists == 'false'
 3        run: |
 4          git config --local user.email "action@gitea.local"
 5          git config --local user.name "Gitea Action Bot"
 6          git add README.md
 7          if [[ "${{ steps.version.outputs.PRERELEASE }}" == "true" ]]; then
 8            git commit -m "Update README for pre-release ${{ steps.metadata.outputs.VERSION  }}" || echo "No changes to commit"
 9          else
10            git commit -m "Update README for release ${{ steps.metadata.outputs.VERSION  }}" || echo "No changes to commit"
11          fi

Commit the README

 1      - name: Create tag
 2        if: steps.check_tag.outputs.exists == 'false'
 3        run: |
 4          git tag "${{ steps.metadata.outputs.VERSION  }}"
 5          git push origin main
 6          git push origin "${{ steps.metadata.outputs.VERSION  }}"
 7      - name: Create release
 8        id: create_release
 9        if: steps.check_tag.outputs.exists == 'false'
10        run: |
11          if [[ "${{ steps.version.outputs.PRERELEASE }}" == "true" ]]; then
12            RELEASE_NAME="🚧 Pre-release ${{ steps.metadata.outputs.VERSION }}"
13            RELEASE_BODY="Pre-release ${{ steps.metadata.outputs.VERSION }} - This is a pre-release version. \
14            It may contain bugs and is not recommended for production use. Use with caution!\n\n\n \
15            ![]($BASE_URL/${{ gitea.repository }}/releases/download/${{ steps.metadata.outputs.VERSION }}/${{ steps.metadata.outputs.LONG_NAME }}-${{ steps.metadata.outputs.VERSION }}-Top-3D.png)"
16          else
17            RELEASE_NAME="πŸš€ Release ${{ steps.metadata.outputs.VERSION }}"
18            RELEASE_BODY="Release ${{ steps.metadata.outputs.VERSION }}\n\n\n \
19            ![]($BASE_URL/${{ gitea.repository }}/releases/download/${{ steps.metadata.outputs.VERSION }}/${{ steps.metadata.outputs.LONG_NAME }}-${{ steps.metadata.outputs.VERSION }}-Top-3D.png)"
20          fi
21
22          # Simple JSON without complex escaping
23          RESPONSE=$(curl -X POST \
24            -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
25            -H "Content-Type: application/json" \
26            -d "{\"tag_name\":\"${{ steps.metadata.outputs.VERSION }}\",\"name\":\"$RELEASE_NAME\",\"body\":\"$RELEASE_BODY\",\"draft\":false,\"prerelease\":${{ steps.version.outputs.PRERELEASE }}}" \
27            "${{ gitea.api_url }}/repos/${{ gitea.repository }}/releases")
28
29          # Extract release ID and upload URL
30          RELEASE_ID=$(echo "$RESPONSE" | jq -r '.id')
31          echo "RELEASE_ID=$RELEASE_ID" >> $GITHUB_OUTPUT
32
33          echo "Release created with ID: $RELEASE_ID"
34      - name: Release summary
35        id: release_summary
36        if: steps.check_tag.outputs.exists == 'false'
37        run: |
38          if [[ "${{ steps.version.outputs.PRERELEASE }}" == "true" ]]; then
39            echo "βœ… Pre-release ${{ steps.metadata.outputs.VERSION  }} created successfully!"
40          else
41            echo "πŸŽ‰ Stable release ${{ steps.metadata.outputs.VERSION  }} created successfully!"
42          fi

Create a tag, create a release (with a path to a not yet existing 3D rendered image of the board) and finally print a success message

Now we enter the second job where we generate the production files and images.

1  production-file-export:
2    name: Export Production files
3    needs: release
4    runs-on: ubuntu-latest

This needs the release job to have succeeded in order to run

1    steps:
2      - name: Checkout Repository
3        uses: actions/checkout@v4
4        with:
5          fetch-depth: 0
6      - name: Install zip
7        run: |
8          apt-get update && apt-get install -y zip

Checkout the repo and install zip, we need that later.

 1      - name: Load metadata from metadata.env file
 2        id: metadata
 3        run: |
 4          set -a
 5          source metadata.env
 6          set +a
 7          echo "DATE=$(date +%Y-%m-%d)" >> $GITHUB_OUTPUT
 8          echo "YEAR=$(date +%Y)" >> $GITHUB_OUTPUT
 9          echo "LONG_NAME=$LONG_NAME" >> $GITHUB_OUTPUT
10          echo "SHORT_NAME=$SHORT_NAME" >> $GITHUB_OUTPUT
11          echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
12          echo "AUTHOR=$AUTHOR" >> $GITHUB_OUTPUT
13          echo "KIBOT_CONFIG=$KIBOT_CONFIG" >> $GITHUB_OUTPUT
14          echo "PCB_FILE=$PCB_FILE" >> $GITHUB_OUTPUT
15          echo "SCH_FILE=$SCH_FILE" >> $GITHUB_OUTPUT

Again read the metadata.env and store the values into $GITHUB_OUTPUT

 1      - name: Update Files with correct Version and Date
 2        run: |
 3          sed -i "s/(rev \"[^\"]*\")/(rev \"${{ steps.metadata.outputs.VERSION }}\")/" ${{ steps.metadata.outputs.PCB_FILE}}
 4          sed -i "s/(date \"[^\"]*\")/(date \"${{ steps.metadata.outputs.DATE }}\")/" ${{ steps.metadata.outputs.PCB_FILE}}
 5          sed -i "s/<[vV]>/${{ steps.metadata.outputs.VERSION }}/g" ${{ steps.metadata.outputs.PCB_FILE}}
 6          sed -i "s/<[yY]>/${{ steps.metadata.outputs.YEAR }}/g" ${{ steps.metadata.outputs.PCB_FILE}}
 7          sed -i "s/<[aA]>/${{ steps.metadata.outputs.AUTHOR }}/g" ${{ steps.metadata.outputs.PCB_FILE}}
 8          sed -i "s/<[sS]>/${{ steps.metadata.outputs.SHORT_NAME }}/g" ${{ steps.metadata.outputs.PCB_FILE}}
 9          sed -i "s/(rev \"[^\"]*\")/(rev \"${{ steps.metadata.outputs.VERSION }}\")/" ${{ steps.metadata.outputs.SCH_FILE}}
10          sed -i "s/(date \"[^\"]*\")/(date \"${{ steps.metadata.outputs.DATE }}\")/" ${{ steps.metadata.outputs.SCH_FILE}}

Here we replace placeholders in the kicad_pcb file, <V> is replaced with the Version, <Y> is replaced with the current Year, <A> is replaced with the Author, <S> is replaced with the short name. We also update Version and date of the PCB and schematic so that the exported schematic hast the correct version in the frame.

1      - name: Run KiBot for Teltronik Production files
2        uses: INTI-CMNB/KiBot@v2_k9
3        with:
4          config: ${{ steps.metadata.outputs.KIBOT_CONFIG }}
5          dir: ./output
6          schema: ${{ steps.metadata.outputs.SCH_FILE}}
7          board: ${{ steps.metadata.outputs.PCB_FILE}}
8          additional_args: -E LONG_NAME=${{ steps.metadata.outputs.LONG_NAME }}

Now we run KiBot with the config, pcb file and schematic file specified in the metadata.env. We also pass the long name as an extta arg.

 1      - name: Zip results
 2        id: zip
 3        run: |
 4          cd output
 5          zip -r ../${{ steps.metadata.outputs.DATE }}-${{ steps.metadata.outputs.LONG_NAME }}-ProductionFiles-${{ steps.metadata.outputs.VERSION }}.zip . -x  "JLCPCB/*" 
 6          cd ..
 7          echo "PRODUCTION_FILES=${{ steps.metadata.outputs.DATE }}-${{ steps.metadata.outputs.LONG_NAME }}-ProductionFiles-${{ steps.metadata.outputs.VERSION }}.zip" >> $GITHUB_OUTPUT
 8          echo "JLCPCB_GERBER_FILES=$(find output/JLCPCB -maxdepth 1 -iname "*.zip" -type f)" >> $GITHUB_OUTPUT
 9          echo "JLCPCB_BOM_FILE=$(find output/JLCPCB -maxdepth 1 -iname "*bom*" -type f)" >> $GITHUB_OUTPUT
10          echo "JLCPCB_CPL_FILE=$(find output/JLCPCB -maxdepth 1 -iname "*cpl*" -type f)" >> $GITHUB_OUTPUT

Finally we zip the generated files, except the JLCPCB files because these are not meant for the European manufacturer. The path to the resulting zip is again stored in a variable for later use. The JLCPCB file (zip with gerbers/excellon, BOM and CPL) are also stored in separate variables.

 1      - name: Upload Release artifacts
 2        env:
 3          KIBOT_CONFIG: ${{ steps.metadata.outputs.KIBOT_CONFIG }}
 4        run: |
 5          curl -v -X POST \
 6            -H "Authorization: token ${{ secrets.TOKEN }}" \
 7            -H "Content-Type: application/octet-stream" \
 8            -T ./output/${{ steps.metadata.outputs.LONG_NAME }}-${{ steps.metadata.outputs.VERSION }}-Top.png \
 9            "${{ gitea.api_url }}/repos/${{ gitea.repository }}/releases/${{ needs.release.outputs.RELEASE_ID }}/assets?name=${{ steps.metadata.outputs.LONG_NAME }}-${{ steps.metadata.outputs.VERSION }}-Top.png"
10          curl -v -X POST \
11            -H "Authorization: token ${{ secrets.TOKEN }}" \
12            -H "Content-Type: application/octet-stream" \
13            -T ./output/${{ steps.metadata.outputs.LONG_NAME }}-${{ steps.metadata.outputs.VERSION }}-Top-3D.png \
14            "${{ gitea.api_url }}/repos/${{ gitea.repository }}/releases/${{ needs.release.outputs.RELEASE_ID }}/assets?name=${{ steps.metadata.outputs.LONG_NAME }}-${{ steps.metadata.outputs.VERSION }}-Top-3D.png"
15          if [[ "${KIBOT_CONFIG}" == *"double"* ]]; then
16            curl -v -X POST \
17              -H "Authorization: token ${{ secrets.TOKEN }}" \
18              -H "Content-Type: application/octet-stream" \
19              -T ./output/${{ steps.metadata.outputs.LONG_NAME }}-${{ steps.metadata.outputs.VERSION }}-Bottom.png \
20              "${{ gitea.api_url }}/repos/${{ gitea.repository }}/releases/${{ needs.release.outputs.RELEASE_ID }}/assets?name=${{ steps.metadata.outputs.LONG_NAME }}-${{ steps.metadata.outputs.VERSION }}-Bottom.png"
21            curl -v -X POST \
22              -H "Authorization: token ${{ secrets.TOKEN }}" \
23              -H "Content-Type: application/octet-stream" \
24              -T ./output/${{ steps.metadata.outputs.LONG_NAME }}-${{ steps.metadata.outputs.VERSION }}-Bottom-3D.png \
25              "${{ gitea.api_url }}/repos/${{ gitea.repository }}/releases/${{ needs.release.outputs.RELEASE_ID }}/assets?name=${{ steps.metadata.outputs.LONG_NAME }}-${{ steps.metadata.outputs.VERSION }}-Bottom-3D.png"
26          fi
27          curl -v -X POST \
28            -H "Authorization: token ${{ secrets.TOKEN }}" \
29            -H "Content-Type: application/octet-stream" \
30            -T ${{ steps.zip.outputs.PRODUCTION_FILES }} \
31            "${{ gitea.api_url }}/repos/${{ gitea.repository }}/releases/${{ needs.release.outputs.RELEASE_ID }}/assets?name=${{ steps.zip.outputs.PRODUCTION_FILES }}"
32          curl -v -X POST \
33            -H "Authorization: token ${{ secrets.TOKEN }}" \
34            -H "Content-Type: application/octet-stream" \
35            -T ${{ steps.zip.outputs.JLCPCB_GERBER_FILES }} \
36            "${{ gitea.api_url }}/repos/${{ gitea.repository }}/releases/${{ needs.release.outputs.RELEASE_ID }}/assets?name=JLCPCB-${{ steps.metadata.outputs.LONG_NAME }}-${{ steps.metadata.outputs.VERSION }}-Gerber.zip"
37          curl -v -X POST \
38            -H "Authorization: token ${{ secrets.TOKEN }}" \
39            -H "Content-Type: application/octet-stream" \
40            -T ${{ steps.zip.outputs.JLCPCB_BOM_FILE }} \
41            "${{ gitea.api_url }}/repos/${{ gitea.repository }}/releases/${{ needs.release.outputs.RELEASE_ID }}/assets?name=JLCPCB-${{ steps.metadata.outputs.LONG_NAME }}-${{ steps.metadata.outputs.VERSION }}-BOM.csv"
42          curl -v -X POST \
43            -H "Authorization: token ${{ secrets.TOKEN }}" \
44            -H "Content-Type: application/octet-stream" \
45            -T ${{ steps.zip.outputs.JLCPCB_CPL_FILE }} \
46            "${{ gitea.api_url }}/repos/${{ gitea.repository }}/releases/${{ needs.release.outputs.RELEASE_ID }}/assets?name=JLCPCB-${{ steps.metadata.outputs.LONG_NAME }}-${{ steps.metadata.outputs.VERSION }}-CPL.csv"

Finally we upload the files as release artifacts. If the string “double” occurs in the kibot config we upload Bottom files as well.

Readme.md πŸ”—

In the Readme.md I always add the name of the project on top, followed by the version, for example # Smart-Merter-Interface V1.0. The Version will be automatically replaced by the action, so it doesn’t matter what exact version we put in here as long as its in the right format.

I also put two links to the front and if double sided, bottom pictures

1![Top view of the PCB](https://gitea.bouni.de/Bouni/smart-meter-interface/releases/download/V1.0/Smart-Meter-Interface-V1.0-Top-3D.png)
2
3![Bottom view of the PCB](https://gitea.bouni.de/Bouni/smart-meter-interface/releases/download/V1.0/Smart-Meter-Interface-V1.0-Bottom-3D.png)

The versions in these URLs are also replaced by the action

Page settings πŸ”—

Another important step is to ensure that in the page settings of both, schematic and pcb the date, the version and the description are filled in.

Important settings πŸ”—

Before we push the files to the Gitea instance, we need to prepare the repo.

First we need to enable Releases and Actions

Next we must add an action secret named TOKEN

The token mus have the following permissions

  • write:package
  • write:repository

A token can be generated under your user settings -> Applications -> Access Tokens

The result πŸ”—

When everything goes according to plan, we end up with a release that has a 3D rendered picture of the Top side of the PCB in the description as well as several attached artifacts.

Also the README.md has nice renderings of the top and bottom now