Compare commits

...
Author SHA1 Message Date
Ludy 0e2b1ee3f7 Merge branch 'main' into custom_task_20260812 2026-08-25 00:20:59 +02:00
Ludy cc42074b45 Merge branch 'main' into custom_task_20260812 2026-08-21 09:44:49 +02:00
Ludy87 4133c07476 Update action.yml 2026-08-20 23:35:17 +02:00
Ludy87 feaccebf01 Update action.yml 2026-08-20 23:30:24 +02:00
Ludy87 7861e33324 Update action.yml 2026-08-20 23:25:10 +02:00
Ludy87 e5f67e09c7 Install Task from release when Docker missing
Update the setup-task action to prefer the CI image when Docker is available, and fall back to downloading a verified release archive when it is not. The Linux step now detects Docker; otherwise it maps runner arch (X64/ARM64), downloads the appropriate task tarball and checksums from GitHub Releases, verifies the SHA256, and extracts the task binary. Also improves curl retry/options and updates the action description/step name to reflect behavior. This ensures Task can be installed securely on runners without Docker.
2026-08-20 23:19:33 +02:00
Ludy a574772226 Merge branch 'main' into custom_task_20260812 2026-08-20 23:15:11 +02:00
Ludy 536b46faea Merge branch 'main' into custom_task_20260812 2026-08-20 21:39:34 +02:00
Ludy87 5fc37ac46c Merge branch 'custom_task_20260812' of https://github.com/Stirling-Tools/Stirling-PDF into custom_task_20260812 2026-08-18 21:53:34 +02:00
Ludy87 beb232f9e1 Bump Task to 3.53.1 in CI and Dockerfiles
Upgrade the pinned Task CLI to 3.53.1 across CI and Docker build configs. Updated defaults and ARGs in .github/actions/setup-task/action.yml, .github/workflows/publish-ci-image.yml, docker/ci/Dockerfile, docker/embedded/Dockerfile(.fat,.ultra-lite) and engine/Dockerfile. Also updated platform-specific SHA256 checksums for the Task archives and clarified the action description to note native runner support.
2026-08-18 21:51:40 +02:00
Ludy 3106063e80 Merge branch 'main' into custom_task_20260812 2026-08-18 21:43:01 +02:00
Ludy a1489faaf1 Merge branch 'main' into custom_task_20260812 2026-08-17 10:22:53 +02:00
Ludy 9ff56af1f4 Merge branch 'main' into custom_task_20260812 2026-08-16 15:47:45 +02:00
Ludy 8f2a967141 Merge branch 'main' into custom_task_20260812 2026-08-13 00:17:39 +02:00
Ludy87 061e5d8b17 Update action.yml 2026-08-13 00:13:26 +02:00
Ludy87 0ef46240d9 Update Dockerfile 2026-08-12 22:33:48 +02:00
Ludy87 e4a31a6e6e Update dependabot.yml 2026-08-12 22:26:38 +02:00
Ludy87 b23ad4dc67 Update Dockerfile 2026-08-12 22:22:13 +02:00
Ludy87 67ff7372b7 Update docker.yml 2026-08-12 22:09:48 +02:00
Ludy87 885331e7ee Update Dockerfile 2026-08-12 22:09:34 +02:00
Ludy87 761a71e2f6 Update action.yml 2026-08-12 21:47:59 +02:00
Ludy87 e1ece98d13 Update publish-ci-image.yml 2026-08-12 21:42:12 +02:00
Ludy87 c0d0f62b8f Publish Task CI image and pull from GHCR
Change setup-task action to pull a prebuilt Task CI image from ghcr.io instead of building it in-action (remove build/cache logic). Add a new workflow (publish-ci-image.yml) that builds and pushes a multi-arch Docker image (ghcr.io/stirling-tools/stirling-pdf-ci:task-<version>) to GHCR; supports workflow_dispatch with a task-version input (default 3.52.0) and triggers on Dockerfile changes. This separates image publishing from CI setup and simplifies runner setup.
2026-08-12 21:38:36 +02:00
Ludy87 3798e0e370 Use local task setup action in workflows
Replace the pinned external go-task/setup-task GitHub Action with the repo-local ./.github/actions/setup-task in CI workflows. This keeps Task setup consistent across workflows and avoids relying on a duplicated external action reference.
2026-08-12 21:35:31 +02:00
Ludy87 d316946f41 Bump task to 3.52.0 and improve CI setup
Include the requested task version in the GitHub Action cache scope so caches are versioned per-input. Update the CI Dockerfile to use TASK_VERSION 3.52.0, refresh amd64/arm64 sha256 checksums for the new release, and make the download step more robust by adding --http1.1, increased retries, retry-all-errors and a connect timeout.
2026-08-12 21:30:15 +02:00
Ludy87 337df490dc Update Dockerfile 2026-08-12 21:25:19 +02:00
Ludy87 4dbe1028cb Update pre_commit.yml 2026-08-12 21:20:52 +02:00
Ludy87 4a51160a94 Add local setup-task action and CI image
Introduce a local GitHub Action (.github/actions/setup-task/action.yml) to install a pinned go-task binary. On Linux it builds a small Ubuntu CI image (docker/ci/Dockerfile) and extracts /usr/local/bin/task for a cacheable, reproducible install; on macOS/Windows it downloads the official release archive. Update .github/workflows/test-build-docker.yml to use the new local action. Defaults: action TASK_VERSION=3.52.0, Dockerfile ARG TASK_VERSION=3.51.1. Purpose: improved CI reproducibility and caching for Task installs.
2026-08-12 21:18:17 +02:00
29 changed files with 201 additions and 28 deletions
+80
View File
@@ -0,0 +1,80 @@
name: Setup Task from the CI image
description: Install the pinned Task binary from the CI image when Docker is available, or from a verified release archive
inputs:
version:
description: Task version to install
required: false
default: 3.53.1
runs:
using: composite
steps:
- name: Install Task on Linux
if: runner.os == 'Linux'
shell: bash
env:
TASK_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
install_dir="${RUNNER_TEMP}/task-bin"
mkdir -p "${install_dir}"
if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
image="ghcr.io/stirling-tools/stirling-pdf-ci:task-${TASK_VERSION}"
docker pull "${image}"
container_id="$(docker create "${image}")"
cleanup() { docker rm "${container_id}" >/dev/null; }
trap cleanup EXIT
docker cp "${container_id}:/usr/local/bin/task" "${install_dir}/task"
chmod 0755 "${install_dir}/task"
else
case "${RUNNER_ARCH}" in
X64) task_arch=amd64 ;;
ARM64) task_arch=arm64 ;;
*) echo "Unsupported runner architecture: ${RUNNER_ARCH}" >&2; exit 1 ;;
esac
task_file="task_linux_${task_arch}.tar.gz"
archive="${RUNNER_TEMP}/${task_file}"
checksums="${RUNNER_TEMP}/task_checksums.txt"
release_url="https://github.com/go-task/task/releases/download/v${TASK_VERSION}"
curl --fail --silent --show-error --location --http1.1 \
--retry 5 --retry-all-errors --retry-delay 2 --connect-timeout 30 \
"${release_url}/${task_file}" --output "${archive}"
curl --fail --silent --show-error --location --http1.1 \
--retry 5 --retry-all-errors --retry-delay 2 --connect-timeout 30 \
"${release_url}/task_checksums.txt" --output "${checksums}"
(
cd "${RUNNER_TEMP}"
grep " ${task_file}$" "${checksums}" | sha256sum -c -
)
tar -xzf "${archive}" -C "${install_dir}" task
fi
echo "${install_dir}" >> "${GITHUB_PATH}"
- name: Install Task natively
if: runner.os != 'Linux'
shell: bash
env:
TASK_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
install_dir="${RUNNER_TEMP}/task-bin"
mkdir -p "${install_dir}"
case "${RUNNER_OS}-${RUNNER_ARCH}" in
macOS-X64) task_platform=darwin; task_arch=amd64; task_file=task_darwin_amd64.tar.gz; task_sha256=7f1a702d54a789cb818a636039a83df071f4179893133afafa4eba351a7e19ef ;;
macOS-ARM64) task_platform=darwin; task_arch=arm64; task_file=task_darwin_arm64.tar.gz; task_sha256=85d2d96c2380b33d7855b07b3f7a20dc7ca0eda999a26efa0fb5f6f32b366cd7 ;;
Windows-X64) task_platform=windows; task_arch=amd64; task_file=task_windows_amd64.zip; task_sha256=27c0cd248c12cba03d8958d954a3df981c900be885ec9ce5f6a3cdc4e9a19316 ;;
Windows-ARM64) task_platform=windows; task_arch=arm64; task_file=task_windows_arm64.zip; task_sha256=4f7c32c0b5a09aabfd860bcf4ff5649f0483339c1f7f90ad5ee55692da7237b6 ;;
*) echo "Unsupported runner: ${RUNNER_OS}-${RUNNER_ARCH}" >&2; exit 1 ;;
esac
archive="${RUNNER_TEMP}/${task_file}"
curl --fail --silent --show-error --location --http1.1 \
--retry 5 --retry-all-errors --retry-delay 2 --connect-timeout 30 \
"https://github.com/go-task/task/releases/download/v${TASK_VERSION}/${task_file}" \
--output "${archive}"
echo "${task_sha256} ${archive}" | sha256sum -c -
if [[ "${task_platform}" == windows ]]; then
unzip -p "${archive}" task.exe > "${install_dir}/task.exe"
else
tar -xzf "${archive}" -C "${install_dir}" task
fi
echo "${install_dir}" >> "${GITHUB_PATH}"
+1
View File
@@ -31,6 +31,7 @@ updates:
- "/docker/embedded"
- "/docker/frontend"
- "/docker/base"
- "/docker/ci"
- "/docker/engine"
- "/docker/unoserver"
- "/engine"
+1 -1
View File
@@ -353,7 +353,7 @@ jobs:
- name: Install Task for Storybook
if: steps.sb-changes.outputs.storybook == 'true'
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Build and deploy Storybook
id: storybook
@@ -206,7 +206,7 @@ jobs:
distribution: "temurin"
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Run Gradle Command
run: |
if [ "${{ needs.check-comment.outputs.disable_security }}" == "true" ]; then
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
cache-suffix: ai-engine
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Quality-check engine
id: engine-check
+1 -1
View File
@@ -52,7 +52,7 @@ jobs:
distribution: "temurin"
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Check Java formatting (Spotless)
# Runs once per matrix combination - pick the cheapest leg
# (core - no proprietary, no saas) so we don't wait for the
+1 -1
View File
@@ -95,7 +95,7 @@ jobs:
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Install Playwright (chromium only)
run: task e2e:install -- chromium
- name: Build frontend (needed for playwright's vite preview webServer)
+1 -1
View File
@@ -76,7 +76,7 @@ jobs:
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Verify generated models are up to date
id: models-check
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
distribution: "temurin"
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Check licenses for compatibility
run: task backend:licenses:check
env:
+1 -1
View File
@@ -39,7 +39,7 @@ jobs:
distribution: "temurin"
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Generate OpenAPI documentation
run: task backend:swagger
env:
+1 -1
View File
@@ -45,7 +45,7 @@ jobs:
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Install Playwright (chromium only)
run: task e2e:install -- chromium
- name: Build frontend (production bundle for vite preview)
+1 -1
View File
@@ -44,7 +44,7 @@ jobs:
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Build frontend (production bundle for vite preview)
env:
VITE_BUILD_FOR_PREVIEW: "1"
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: a11y gate (changed stories)
run: task frontend:storybook:a11y:changed -- origin/${{ github.base_ref || 'main' }}
- name: Upload scan reports
@@ -97,7 +97,7 @@ jobs:
run: npm ci --ignore-scripts --audit=false --fund=false
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Generate frontend license report (Push only)
if: github.event_name == 'push'
@@ -367,7 +367,7 @@ jobs:
distribution: "temurin"
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Check licenses and generate report
id: license-check
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Quality-check frontend
id: frontend-check
run: task frontend:check:all
+3 -3
View File
@@ -69,7 +69,7 @@ jobs:
distribution: "temurin"
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Get version number
id: versionNumber
run: |
@@ -169,7 +169,7 @@ jobs:
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Build JAR
run: ./gradlew build ${{ matrix.variant.build_frontend && '-PbuildWithFrontend=true' || '' }} -x spotlessApply -x spotlessCheck -x test -x sonarqube
@@ -268,7 +268,7 @@ jobs:
distribution: ${{ matrix.platform == 'windows-11-arm' && 'microsoft' || 'temurin' }}
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
# Build the universal JRE before desktop:prepare so the jlink:runtime
# task short-circuits on its `test -d runtime/jre` status check.
+2 -2
View File
@@ -38,7 +38,7 @@ jobs:
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Install all Playwright browsers
run: task e2e:install
@@ -89,7 +89,7 @@ jobs:
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: a11y gate (every story, ${{ matrix.theme }})
run: task frontend:storybook:a11y:${{ matrix.theme }}
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
cache-suffix: pre-commit
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Run pre-commit checks
run: task pre-commit
+55
View File
@@ -0,0 +1,55 @@
name: Publish Task CI image
on:
workflow_dispatch:
inputs:
task-version:
description: Task version to publish
required: true
default: 3.53.1
type: string
push:
paths:
- docker/ci/Dockerfile
- .github/workflows/publish-ci-image.yml
jobs:
publish:
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set Task version
id: version
shell: bash
env:
INPUT_VERSION: ${{ inputs.task-version }}
run: echo "value=${INPUT_VERSION:-3.53.1}" >> "${GITHUB_OUTPUT}"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Log in to GHCR
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Publish Task CI image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
file: docker/ci/Dockerfile
# GitHub-hosted Linux jobs in this repository run on amd64. Avoid
# QEMU/arm64 here because the image is only consumed by those jobs.
platforms: linux/amd64
push: true
build-args: |
TASK_VERSION=${{ steps.version.outputs.value }}
tags: |
ghcr.io/stirling-tools/stirling-pdf-ci:task-${{ steps.version.outputs.value }}
+1 -1
View File
@@ -88,7 +88,7 @@ jobs:
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Get version number
id: versionNumber
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
+1 -1
View File
@@ -63,7 +63,7 @@ jobs:
SWAGGERHUB_USER: "Frooodle"
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Get version number
id: versionNumber
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
+1 -1
View File
@@ -66,7 +66,7 @@ jobs:
uv sync --project engine --locked --group tools
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Sync translation TOML files
run: |
+1 -1
View File
@@ -212,7 +212,7 @@ jobs:
distribution: ${{ matrix.platform == 'windows-11-arm' && 'microsoft' || 'temurin' }}
- name: Setup Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Build universal macOS JRE
if: matrix.platform == 'macos-15'
+1 -1
View File
@@ -127,7 +127,7 @@ jobs:
distribution: "temurin"
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: ./.github/actions/setup-task
- name: Build application
run: task backend:build
env:
+5
View File
@@ -36,6 +36,11 @@ tasks:
cmds:
- docker build -t stirling-pdf-engine .
build:ci:
desc: "Build the Ubuntu CI image with Task installed"
cmds:
- docker build --tag stirling-pdf-ci --file docker/ci/Dockerfile .
up:
desc: "Start standard docker compose stack"
cmds:
+32
View File
@@ -0,0 +1,32 @@
# Lightweight Ubuntu image with a pinned Task binary for CI.
# Build with: task docker:build:ci
FROM ubuntu:noble@sha256:561618e2c15bf2397621dd04f96926663a3b5616c189cf7e38db7e82f5c538ea
ARG TASK_VERSION=3.53.1
ARG TARGETARCH=amd64
RUN set -eux; \
apt-get update; \
apt-get install --no-install-recommends -y ca-certificates curl tar; \
rm -rf /var/lib/apt/lists/*; \
case "${TARGETARCH}" in \
amd64) task_arch=amd64; task_sha256=a54a408f6861ff921f6e87774180db31bacd8c1e7c944ca696db9fea49a82fc7 ;; \
arm64) task_arch=arm64; task_sha256=e3ad19101493a0112e1f22ae8ccc54bf03e533b1076a0ca1e6c782a09ad2e588 ;; \
*) echo "Unsupported Docker architecture: ${TARGETARCH}" >&2; exit 1 ;; \
esac; \
archive="/tmp/task_linux_${task_arch}.tar.gz"; \
curl --fail --show-error --location --http1.1 --retry 5 --retry-all-errors --connect-timeout 30 \
"https://github.com/go-task/task/releases/download/v${TASK_VERSION}/task_linux_${task_arch}.tar.gz" \
--output "${archive}"; \
echo "${task_sha256} ${archive}" | sha256sum --check; \
tar --extract --file "${archive}" --directory /usr/local/bin task; \
chmod 0755 /usr/local/bin/task; \
rm "${archive}"
RUN useradd --create-home --uid 10001 --shell /usr/sbin/nologin taskuser
WORKDIR /workspace
USER taskuser
ENTRYPOINT ["task"]
+1 -1
View File
@@ -7,7 +7,7 @@ ARG BASE_IMAGE=stirlingtools/stirling-pdf-base:${BASE_VERSION}
# Stage 1: Build the Java application and frontend
FROM gradle:9.7.1-jdk25@sha256:a80276ab804c348989df46016e2b5d58cad07c5b29e06f2112434d28ca5b2844 AS app-build
ARG TASK_VERSION=3.52.0
ARG TASK_VERSION=3.53.1
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl ca-certificates \
&& update-ca-certificates \
+1 -1
View File
@@ -10,7 +10,7 @@ ARG BASE_IMAGE=stirlingtools/stirling-pdf-base:${BASE_VERSION}
# Stage 1: Build the Java application and frontend
FROM gradle:9.7.1-jdk25@sha256:a80276ab804c348989df46016e2b5d58cad07c5b29e06f2112434d28ca5b2844 AS app-build
ARG TASK_VERSION=3.52.0
ARG TASK_VERSION=3.53.1
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl ca-certificates \
&& update-ca-certificates \
+1 -1
View File
@@ -7,7 +7,7 @@
FROM gradle:9.7.1-jdk25@sha256:a80276ab804c348989df46016e2b5d58cad07c5b29e06f2112434d28ca5b2844 AS build
# Install Node.js and npm for frontend build
ARG TASK_VERSION=3.52.0
ARG TASK_VERSION=3.53.1
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \