fix(unitpreferences): fix the shipped custom unit conversions #1549
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Docker development container | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| on: | |
| push: | |
| branches: | |
| - master | |
| - 'build-docker' | |
| tags: | |
| - '*' | |
| - '!v*' | |
| workflow_dispatch: | |
| inputs: | |
| ubuntu_24_04_node_24: | |
| description: 'Ubuntu 24.04 + Node 24.x' | |
| type: boolean | |
| default: true | |
| ubuntu_26_04_node_24: | |
| description: 'Ubuntu 26.04 + Node 24.x' | |
| type: boolean | |
| default: true | |
| ubuntu_24_04_node_26: | |
| description: 'Ubuntu 24.04 + Node 26.x' | |
| type: boolean | |
| default: true | |
| ubuntu_26_04_node_26: | |
| description: 'Ubuntu 26.04 + Node 26.x' | |
| type: boolean | |
| default: true | |
| env: | |
| GHCR_REGISTRY: ghcr.io | |
| GHCR_REPO: signalk/signalk-server | |
| GHCR_BASE_REPO: signalk/signalk-server-base | |
| DOCKER_HUB_REPO: signalk/signalk-server | |
| jobs: | |
| signalk-server_npm_files: | |
| if: github.repository == 'SignalK/signalk-server' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Node setup | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '24.x' | |
| - name: Build npm files locally and upload artifacts | |
| run: | | |
| npm cache clean -f | |
| npm install npm@latest -g | |
| npm install --package-lock-only | |
| npm ci && npm cache clean --force | |
| npm run build:all | |
| npm pack --workspaces | |
| rm signalk-typedoc-signalk-theme*.tgz # This is only needed as a dev dependency | |
| npm pack | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| retention-days: 1 | |
| name: packed-modules | |
| path: | | |
| *.tgz | |
| prepare: | |
| name: Prepare matrices | |
| if: github.repository == 'SignalK/signalk-server' | |
| needs: [signalk-server_npm_files] | |
| runs-on: ubuntu-latest | |
| outputs: | |
| build_matrix: ${{ steps.matrix.outputs.build_matrix }} | |
| variant_matrix: ${{ steps.matrix.outputs.variant_matrix }} | |
| any_enabled: ${{ steps.matrix.outputs.any_enabled }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Compute matrices | |
| id: matrix | |
| env: | |
| # Push / tag runs leave inputs empty -> fall back to default_enabled in build-matrix.json. | |
| ENABLED_JSON: ${{ toJSON(github.event.inputs) }} | |
| run: | | |
| set -euo pipefail | |
| # Cross-product os_variants x node_versions x editions from | |
| # .github/build-matrix.json, then filter by the per-row dispatch | |
| # checkbox (or by default_enabled for non-dispatch runs); editions | |
| # are not dispatch inputs and follow their own default_enabled. | |
| # The per-variant suffix preserves the existing SignalK manifest tag | |
| # convention ('-<node>' for ubuntu, '-<node_id>-<family>' otherwise), | |
| # then appends each os_variant's own tag_suffix ('' for the primary | |
| # row of a family, e.g. '-26.04' for a secondary ubuntu row) so rows | |
| # sharing a family don't collide, and finally the edition tag_suffix | |
| # ('' for full, '-core' for core). | |
| variants=$(jq -c \ | |
| --argjson enabled "${ENABLED_JSON:-null}" ' | |
| (if ($enabled | type) == "object" then $enabled else {} end) as $en | |
| | .os_variants as $os | .node_versions as $nodes | .editions as $eds | |
| | [ $os[] as $o | $nodes[] as $n | $eds[] as $e | | |
| ((if $o.family == $o.id then "" else "\($o.family)_" end) + ($o.id | gsub("[-.]"; "_")) + "_node_" + $n.id) as $key | | |
| { | |
| enabled: ($en[$key] // (if ($o.default_enabled and $n.default_enabled) then "true" else "false" end)), | |
| ed_enabled: $e.default_enabled, | |
| family: $o.family, | |
| os_label: $o.os_arg, | |
| node: (if $o.family == "ubuntu" then "\($n.id).x" else $n.id end), | |
| node_id: $n.id, | |
| edition: $e.id, | |
| tag_suffix: $e.tag_suffix, | |
| suffix: ((if $o.family == "ubuntu" then "-\($n.id).x" else "-\($n.id)-\($o.family)" end) + $o.tag_suffix + $e.tag_suffix) | |
| } | |
| ] | |
| | map(select(.enabled == "true" and .ed_enabled)) | |
| | map(del(.enabled) | del(.ed_enabled)) | |
| ' .github/build-matrix.json) | |
| # Build matrix: cross-product variants x architectures. | |
| # Per-node extra_platforms append to the arch's base platform. | |
| build_matrix=$(jq -c \ | |
| --argjson variants "$variants" ' | |
| (.node_versions | map({(.id): .}) | add) as $node_index | |
| | .architectures as $archs | |
| | [ $variants[] as $v | | |
| $node_index[$v.node_id] as $n | | |
| $archs[] as $a | | |
| select(((($n.exclude_archs // []) | index($a.id)) | not)) | | |
| (($n.extra_platforms // {})[$a.id] // "") as $extra | | |
| ($v + { | |
| arch: $a.id, | |
| vm: $a.vm, | |
| platform: (if $extra == "" then $a.platform else "\($a.platform),\($extra)" end) | |
| }) | |
| ] | |
| ' .github/build-matrix.json) | |
| # Variant matrix: one row per (OS x Node x Edition) variant (used by | |
| # manifest + copy jobs). archs is derived from build_matrix so the | |
| # manifest sources reflect the architectures actually built | |
| # (respecting exclude_archs). tag_suffix discriminates editions that | |
| # share the same OS x Node. | |
| variant_matrix=$(jq -cn \ | |
| --argjson variants "$variants" \ | |
| --argjson build_matrix "$build_matrix" ' | |
| [ $variants[] as $v | | |
| ($v + { | |
| archs: [ $build_matrix[] | |
| | select(.os_label == $v.os_label and .node == $v.node and .edition == $v.edition) | |
| | .arch ] | |
| }) | |
| | select(.archs | length > 0) | |
| | {os_label, node, edition, tag_suffix, suffix, archs} | |
| ] | |
| ') | |
| count=$(echo "$variant_matrix" | jq 'length') | |
| if [[ "$count" -gt 0 ]]; then | |
| echo "any_enabled=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "any_enabled=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| echo "build_matrix=$build_matrix" >> "$GITHUB_OUTPUT" | |
| echo "variant_matrix=$variant_matrix" >> "$GITHUB_OUTPUT" | |
| echo "Enabled variants:" | |
| echo "$variants" | jq | |
| build: | |
| name: Build ${{ matrix.os_label }} node-${{ matrix.node }} ${{ matrix.edition }} ${{ matrix.arch }} | |
| needs: [prepare] | |
| if: needs.prepare.outputs.any_enabled == 'true' | |
| runs-on: ${{ matrix.vm }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJSON(needs.prepare.outputs.build_matrix) }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Login to ghcr.io | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ${{ env.GHCR_REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GHCR_PAT }} | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: packed-modules | |
| - name: Build and push | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| file: ./docker/Dockerfile | |
| platforms: ${{ matrix.platform }} | |
| push: true | |
| tags: ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_REPO }}:${{ matrix.arch }}-${{ matrix.os_label }}-${{ matrix.node }}${{ matrix.tag_suffix }}-${{ github.run_id }} | |
| build-args: | | |
| REGISTRY=${{ env.GHCR_REGISTRY }} | |
| BASE_IMAGE=${{ matrix.os_label }}-${{ matrix.node }} | |
| EDITION=${{ matrix.edition }} | |
| manifest: | |
| name: Manifest ${{ matrix.os_label }} node-${{ matrix.node }} ${{ matrix.edition }} | |
| needs: [prepare, build] | |
| if: ${{ !cancelled() && needs.prepare.outputs.any_enabled == 'true' && needs.build.result == 'success' }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJSON(needs.prepare.outputs.variant_matrix) }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Docker meta | |
| id: docker_meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: | | |
| ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_REPO }} | |
| tags: | | |
| type=ref,event=branch | |
| type=ref,event=tag | |
| type=sha | |
| flavor: | | |
| suffix=${{ matrix.suffix }} | |
| - name: Login to ghcr.io | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ${{ env.GHCR_REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GHCR_PAT }} | |
| - name: Compute manifest sources | |
| id: sources | |
| env: | |
| ARCHS_JSON: ${{ toJSON(matrix.archs) }} | |
| SRC_PREFIX: ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_REPO }} | |
| OS: ${{ matrix.os_label }} | |
| NODE: ${{ matrix.node }} | |
| TAG_SUFFIX: ${{ matrix.tag_suffix }} | |
| RUN_ID: ${{ github.run_id }} | |
| run: | | |
| set -euo pipefail | |
| srcs=$(jq -r \ | |
| --arg p "$SRC_PREFIX" --arg o "$OS" --arg n "$NODE" --arg ts "$TAG_SUFFIX" --arg r "$RUN_ID" \ | |
| '.[] | "\($p):\(.)-\($o)-\($n)\($ts)-\($r)"' <<<"$ARCHS_JSON") | |
| { | |
| echo 'value<<EOF' | |
| echo "$srcs" | |
| echo EOF | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Create and push multi-arch manifest to GHCR | |
| uses: int128/docker-manifest-create-action@v2 | |
| with: | |
| tags: | | |
| ${{ steps.docker_meta.outputs.tags }} | |
| sources: | | |
| ${{ steps.sources.outputs.value }} | |
| - name: Save tags to file | |
| run: | | |
| mkdir -p /tmp/tags | |
| echo "${{ steps.docker_meta.outputs.tags }}" > /tmp/tags/${{ matrix.node }}${{ matrix.tag_suffix }}.txt | |
| - name: Upload tag artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ubuntu-tag-${{ matrix.os_label }}-${{ matrix.node }}${{ matrix.tag_suffix }} | |
| path: /tmp/tags/${{ matrix.node }}${{ matrix.tag_suffix }}.txt | |
| retention-days: 1 | |
| copy-to-dockerhub: | |
| name: Copy ${{ matrix.os_label }} node-${{ matrix.node }} ${{ matrix.edition }} to Docker Hub | |
| needs: [prepare, manifest] | |
| if: ${{ !cancelled() && needs.prepare.outputs.any_enabled == 'true' && needs.manifest.result == 'success' }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJSON(needs.prepare.outputs.variant_matrix) }} | |
| steps: | |
| - name: Download tag artifact | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: ubuntu-tag-${{ matrix.os_label }}-${{ matrix.node }}${{ matrix.tag_suffix }} | |
| path: /tmp/tags | |
| - name: Install skopeo | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y skopeo | |
| - name: Copy GHCR -> Docker Hub | |
| shell: bash | |
| env: | |
| GHCR_USERNAME: ${{ github.actor }} | |
| GHCR_TOKEN: ${{ secrets.GHCR_PAT }} | |
| DOCKER_HUB_USERNAME: signalkci | |
| DOCKER_HUB_ACCESS_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} | |
| HUB_PREFIX: docker.io/${{ env.DOCKER_HUB_REPO }} | |
| run: | | |
| set -euo pipefail | |
| TAGS_FILE="/tmp/tags/${{ matrix.node }}${{ matrix.tag_suffix }}.txt" | |
| while IFS= read -r FULL_TAG || [ -n "${FULL_TAG:-}" ]; do | |
| [ -z "${FULL_TAG:-}" ] && continue | |
| TAG="${FULL_TAG##*:}" | |
| echo "Copying: ${FULL_TAG} -> ${HUB_PREFIX}:${TAG}" | |
| skopeo copy --all \ | |
| --src-creds "${GHCR_USERNAME}:${GHCR_TOKEN}" \ | |
| --dest-creds "${DOCKER_HUB_USERNAME}:${DOCKER_HUB_ACCESS_TOKEN}" \ | |
| "docker://${FULL_TAG}" \ | |
| "docker://${HUB_PREFIX}:${TAG}" | |
| done < "$TAGS_FILE" | |
| record-image-sizes: | |
| name: Record image sizes | |
| needs: [prepare, manifest] | |
| if: ${{ !cancelled() && needs.prepare.outputs.any_enabled == 'true' && needs.manifest.result == 'success' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout metrics branch | |
| uses: actions/checkout@v7 | |
| with: | |
| ref: metrics | |
| - name: Record image sizes | |
| env: | |
| GHCR_CREDS: '${{ github.actor }}:${{ secrets.GHCR_PAT }}' | |
| REGISTRY: ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_REPO }} | |
| BUILD_MATRIX: ${{ needs.prepare.outputs.build_matrix }} | |
| run: | | |
| set -euo pipefail | |
| TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) | |
| COMMIT="${{ github.sha }}" | |
| CSV="master-container_sizes.csv" | |
| RUN_ID="${{ github.run_id }}" | |
| if [ ! -f "$CSV" ]; then | |
| echo "timestamp,commit,tag,size_bytes" > "$CSV" | |
| fi | |
| # Tags are derived from the prepare job's build_matrix to stay in sync | |
| # with whichever OS x Node x Arch x Edition combinations were actually built. | |
| jq -r '.[] | "\(.arch)-\(.os_label)-\(.node)\(.tag_suffix)"' <<<"$BUILD_MATRIX" \ | |
| | while IFS= read -r BASE_TAG; do | |
| TAG="${BASE_TAG}-${RUN_ID}" | |
| DIGEST=$(skopeo inspect --raw --creds "${GHCR_CREDS}" \ | |
| "docker://${REGISTRY}:${TAG}" | \ | |
| jq -r '.manifests[0].digest') | |
| SIZE=$(skopeo inspect --raw --creds "${GHCR_CREDS}" \ | |
| "docker://${REGISTRY}@${DIGEST}" | \ | |
| jq '[.layers[].size] | add') | |
| echo "${TIMESTAMP},${COMMIT},${BASE_TAG},${SIZE}" >> "$CSV" | |
| done | |
| - name: Commit and push | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add master-container_sizes.csv | |
| git commit -m "chore: record container sizes for ${GITHUB_SHA::7}" | |
| git push | |
| housekeeping: | |
| name: Housekeeping | |
| needs: [copy-to-dockerhub, record-image-sizes] | |
| if: ${{ always() && needs.copy-to-dockerhub.result == 'success' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| packages: write | |
| steps: | |
| - name: Wait for GHCR indexing | |
| run: sleep 60 | |
| - name: Remove intermediate Docker images from GHCR | |
| continue-on-error: true | |
| uses: dataaxiom/ghcr-cleanup-action@v1 | |
| with: | |
| packages: signalk-server | |
| delete-untagged: true | |
| delete-tags: | | |
| *-${{ github.run_id }} | |
| token: ${{ secrets.GHCR_PAT }} |