Skip to content

fix(unitpreferences): fix the shipped custom unit conversions #1549

fix(unitpreferences): fix the shipped custom unit conversions

fix(unitpreferences): fix the shipped custom unit conversions #1549

Workflow file for this run

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 }}