diff --git a/.github/scripts/download_archive.sh b/.github/scripts/download_archive.sh new file mode 100755 index 0000000..a73e751 --- /dev/null +++ b/.github/scripts/download_archive.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# Copyright 2026 Elektrobit. All rights reserved. +# +# Download the upstream toolkit archive, extract it into a temporary directory +# and print the resulting paths as KEY=VALUE lines on stdout. +# +# Usage: download_archive.sh --url --token +set -euo pipefail + +url="" +token="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --url) url="$2"; shift 2 ;; + --token) token="$2"; shift 2 ;; + *) echo "Unknown argument: $1" >&2; exit 1 ;; + esac +done + +if [[ -z "${url}" ]]; then + echo "ERROR: Required argument --url is not set." >&2 + exit 1 +fi + +if [[ -z "${token}" ]]; then + echo "ERROR: Required argument --token is not set." >&2 + exit 1 +fi + +tmp_dir="$(mktemp -d)" +archive_path="${tmp_dir}/eb_corbos_toolkit.tar.gz" +extract_root="${tmp_dir}/extracted" + +mkdir -p "${extract_root}" + +echo "Starting upstream delivery archive download (this may take a while for large files)..." >&2 +echo "Download target: ${archive_path}" >&2 + +curl \ + --fail \ + --progress-bar \ + --show-error \ + --location \ + --retry 3 \ + --retry-delay 5 \ + --retry-all-errors \ + --header "Authorization: Bearer ${token}" \ + --output "${archive_path}" \ + --url "${url}" + +archive_size_bytes="$(wc -c < "${archive_path}")" +archive_size_mib="$(awk "BEGIN { printf \"%.2f\", ${archive_size_bytes}/1024/1024 }")" +echo "Download complete: ${archive_size_bytes} bytes (${archive_size_mib} MiB)." >&2 + +echo "Starting upstream delivery archive extraction into ${extract_root}..." >&2 + +tar -xzf "${archive_path}" -C "${extract_root}" + +echo "Extraction complete." >&2 + +toolkit_root="${extract_root}/eb_corbos_toolkit" +workspace_root="${toolkit_root}/workspace" +devcontainer_archive="${toolkit_root}/containers/devcontainer-trixie-ebclfsa-amd64.docker-archive.zst" +buildcontainer_archive="${toolkit_root}/containers/buildcontainer-trixie-ebclfsa-amd64.docker-archive.zst" +run_script="${workspace_root}/scripts/run.sh" +bitbake_execution_fixer="${workspace_root}/.devcontainer/scripts/check_userns_restriction.sh" +manual_pdf="${toolkit_root}/doc/user_manual.pdf" +manual_html_dir="${toolkit_root}/doc/html" +prebuilt_dir="${workspace_root}/prebuilt" + +echo "Validating extracted layout..." >&2 +for f in "${devcontainer_archive}" "${buildcontainer_archive}" "${run_script}" "${bitbake_execution_fixer}" "${manual_pdf}"; do + if [[ ! -f "${f}" ]]; then + echo "ERROR: Expected file not found in extracted upstream delivery archive: ${f}" >&2 + exit 1 + fi +done +for d in "${manual_html_dir}" "${prebuilt_dir}"; do + if [[ ! -d "${d}" ]]; then + echo "ERROR: Expected directory not found in extracted upstream delivery archive: ${d}" >&2 + exit 1 + fi +done +echo "Extracted layout looks good." >&2 + +cat << EOF +TMP_WORK_DIR=${tmp_dir} +EXTRACT_ROOT=${extract_root} +ARCHIVE_PATH=${archive_path} +WORKSPACE_ROOT=${workspace_root} +DEVCONTAINER_ARCHIVE=${devcontainer_archive} +BUILDCONTAINER_ARCHIVE=${buildcontainer_archive} +DEVCONTAINER_IMAGE=ghcr.io/elektrobit/eb-corbos-toolkit-devcontainer-amd64 +BUILDCONTAINER_IMAGE=ghcr.io/elektrobit/eb-corbos-toolkit-buildcontainer-amd64 +RUN_SCRIPT=${run_script} +BITBAKE_EXECUTION_FIXER=${bitbake_execution_fixer} +MANUAL_PDF=${manual_pdf} +MANUAL_HTML_DIR=${manual_html_dir} +PREBUILT_DIR=${prebuilt_dir} +EOF diff --git a/.github/scripts/integrate_workspace.sh b/.github/scripts/integrate_workspace.sh new file mode 100755 index 0000000..d76760a --- /dev/null +++ b/.github/scripts/integrate_workspace.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# Copyright 2026 Elektrobit. All rights reserved. +# +# Replace the integration repository's working tree with the upstream workspace, +# preserving repository-owned metadata files. Must be run from the root of the +# integration repository checkout. +# +# Usage: integrate_workspace.sh --workspace-root +set -euo pipefail + +workspace_root="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --workspace-root) workspace_root="$2"; shift 2 ;; + *) echo "Unknown argument: $1" >&2; exit 1 ;; + esac +done + +if [[ -z "${workspace_root}" ]]; then + echo "ERROR: Required argument --workspace-root is not set." >&2 + exit 1 +fi + +if [[ ! -d "${workspace_root}" ]]; then + echo "ERROR: Workspace root '${workspace_root}' does not exist." >&2 + exit 1 +fi + +# Remove all tracked and untracked content except the repository metadata and +# files that must remain in the integration repository. Add entries to this list +# when additional repository-owned content should be preserved. +preserve_paths=( + '.git' + '.github' + '.gitignore' + 'CODE_OF_CONDUCT.md' + 'README.md' +) + +while IFS= read -r -d '' path; do + name="${path#./}" + preserve=false + for preserve_path in "${preserve_paths[@]}"; do + if [[ "${name}" == "${preserve_path}" ]]; then + preserve=true + break + fi + done + + if [[ "${preserve}" != true ]]; then + rm -rf -- "${path}" + fi +done < <(find . -mindepth 1 -maxdepth 1 -print0) + +# Copy the upstream workspace on top, preserving flags, mtimes, ownership. +# Ignore the "prebuilt" and "build" sub-folders. +rsync -a --exclude='prebuilt' --exclude='build' "${workspace_root}/." . diff --git a/.github/scripts/package_release_assets.sh b/.github/scripts/package_release_assets.sh new file mode 100755 index 0000000..16738f0 --- /dev/null +++ b/.github/scripts/package_release_assets.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# Copyright 2026 Elektrobit. All rights reserved. +# +# Collect the release assets (toolkit archive, user manual, prebuilt per-target +# images, SDK sysroots and SSH keys) into a single directory and print its path +# as a KEY=VALUE line on stdout. +# +# Usage: package_release_assets.sh --tmp-work-dir --archive-path \ +# --manual-pdf --manual-html-dir --prebuilt-dir +set -euo pipefail + +tmp_work_dir="" +archive_path="" +manual_pdf="" +manual_html_dir="" +prebuilt_dir="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --tmp-work-dir) tmp_work_dir="$2"; shift 2 ;; + --archive-path) archive_path="$2"; shift 2 ;; + --manual-pdf) manual_pdf="$2"; shift 2 ;; + --manual-html-dir) manual_html_dir="$2"; shift 2 ;; + --prebuilt-dir) prebuilt_dir="$2"; shift 2 ;; + *) echo "Unknown argument: $1" >&2; exit 1 ;; + esac +done + +for arg_name in tmp_work_dir archive_path manual_pdf manual_html_dir prebuilt_dir; do + if [[ -z "${!arg_name}" ]]; then + echo "ERROR: Required argument --${arg_name//_/-} is not set." >&2 + exit 1 + fi +done + +assets_dir="${tmp_work_dir}/release-assets" +mkdir -p "${assets_dir}" + +mv "${archive_path}" "${assets_dir}/eb_corbos_toolkit.tar.gz" +cp "${manual_pdf}" "${assets_dir}/user_manual.pdf" +tar -czf "${assets_dir}/user_manual_html.tar.gz" -C "${manual_html_dir}" . + +# Per-target assets: one image archive (named after its .wic file), the SDK +# sysroot tarball and the SSH target key for every target directory. +for target_dir in "${prebuilt_dir}"/*/; do + [[ -d "${target_dir}" ]] || continue + + image_dir="${target_dir}image" + if [[ -d "${image_dir}" ]]; then + wic_file="$(find "${image_dir}" -maxdepth 1 -name '*.wic' -print -quit)" + if [[ -z "${wic_file}" ]]; then + echo "ERROR: No .wic file found in ${image_dir}." >&2 + exit 1 + fi + image_base="$(basename "${wic_file}" .wic)" + tar -czf "${assets_dir}/${image_base}.tar.gz" -C "${image_dir}" . + fi + + sdk_dir="${target_dir}sysroot" + if [[ -d "${sdk_dir}" ]]; then + find "${sdk_dir}" -maxdepth 1 -type f -exec cp {} "${assets_dir}/" \; + fi + + key_dir="${target_dir}ssh_keys" + if [[ -d "${key_dir}" ]]; then + find "${key_dir}" -maxdepth 1 -type f -exec cp {} "${assets_dir}/" \; + fi +done + +echo "RELEASE_ASSETS_DIR=${assets_dir}" diff --git a/.github/scripts/qemu_smoke_test.sh b/.github/scripts/qemu_smoke_test.sh new file mode 100755 index 0000000..571509c --- /dev/null +++ b/.github/scripts/qemu_smoke_test.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# Copyright 2026 Elektrobit. All rights reserved. +# +# Smoke test: boot the freshly built "fastdev" image in QEMU inside the +# devcontainer, wait for SSH availability, power it down cleanly and confirm the +# shutdown message appears in the QEMU log. +# +# Usage: qemu_smoke_test.sh --workspace-root --run-script +set -euo pipefail + +workspace_root="" +run_script="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --workspace-root) workspace_root="$2"; shift 2 ;; + --run-script) run_script="$2"; shift 2 ;; + *) echo "Unknown argument: $1" >&2; exit 1 ;; + esac +done + +if [[ -z "${workspace_root}" ]]; then + echo "ERROR: Required argument --workspace-root is not set." >&2 + exit 1 +fi + +if [[ -z "${run_script}" ]]; then + echo "ERROR: Required argument --run-script is not set." >&2 + exit 1 +fi + +cd "${workspace_root}" + +# Run the dev container in detached mode and keep it running. +export EXTRA_DOCKER_OPTIONS="-d --log-driver local" +LOG_FILE=$(mktemp -t ebcl_run_devcontainer-XXXXXX.log 2>/dev/null) || + LOG_FILE="/tmp/ebcl_run_devcontainer.log" + +echo "Starting devcontainer in the background" >&2 +DEV_CONTAINER_ID=$("${run_script}" -d -- bash -c "sleep infinity" 2>&1 | tee "${LOG_FILE}" | grep -oE '^[0-9a-f]{64}$') +if [[ -z "${DEV_CONTAINER_ID}" ]]; then + echo "ERROR: Failed to start devcontainer. Log output:" >&2 + cat "${LOG_FILE}" >&2 + exit 1 +fi + +echo "DEV_CONTAINER_ID=${DEV_CONTAINER_ID}" +sleep 10 + +devcontainer_exec() { + docker exec "${DEV_CONTAINER_ID}" "$@" +} + +echo "Running fastdev in qemu in background, logging to qemu.log" >&2 +devcontainer_exec bash -c "./scripts/qemu.sh -t fastdev < /dev/null > qemu.log 2>&1 &" >&2 +devcontainer_exec bash -c \ + "log=/workspace/qemu.log; source /workspace/scripts/includes/common/common.inc; wait_for_ssh fastdev-qemuarm64 70 1" >&2 + +echo "Terminating fastdev" >&2 +devcontainer_exec ssh fastdev-qemuarm64 crinit-ctl poweroff >&2 || true + +echo "Waiting for fastdev to power down" >&2 +max_tries=20 +msg="Power down" +log="${workspace_root}/qemu.log" + +for i in $(seq 1 "${max_tries}"); do + if grep -q "${msg}" "${log}" 2>/dev/null; then + echo "Waiting for \"${msg}\" message in ${log} succeeded" >&2 + rm -f "${log}" + exit 0 + fi + echo "Waiting for \"${msg}\" message in ${log} ... (${i}/${max_tries})" >&2 + sleep 1 +done + +echo "ERROR: Waiting for \"${msg}\" message in ${log} timed out!" >&2 +cat "${log}" >&2 +exit 1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..24a2511 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,103 @@ +# Copyright 2026 Elektrobit. All rights reserved. +# +# Execute Release Activities +# +# This workflow shall execute all on-release activities for the EB corbos Toolkit. +# For now, this only publishes the User's Manual to GitHub Pages when a release is created. +# The workflow runs automatically when a release is published, or manually +# via workflow_dispatch by supplying the target release tag. It verifies that +# the release contains the expected User's Manual asset, downloads and extracts +# it, then deploys the contents to GitHub Pages. + +name: Execute Release Activities + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: "Release tag to publish (e.g. v2.0.0-beta2)" + required: true + type: string + +# Only one release should run at a time. +concurrency: + group: release + cancel-in-progress: false + +permissions: + contents: read # read the release + download its assets + pages: write # publish to GitHub Pages + id-token: write # required by the Pages deployment + +env: + USER_GUIDE_ASSET: user_manual_html.tar.gz + # Tag from the manual input when dispatched, otherwise the pushed tag. + RELEASE_TAG: ${{ inputs.tag || github.ref_name }} + +jobs: + execute-release-activities: + name: Execute Release Activities + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + defaults: + run: + shell: bash --noprofile --norc -euo pipefail {0} + + steps: + - name: Verify runner prerequisites + run: | + command -v gh >/dev/null + command -v jq >/dev/null + command -v tar >/dev/null + + - name: Verify published release contains the User's Manual asset + env: + GH_TOKEN: ${{ github.token }} + run: | + tag="${RELEASE_TAG}" + echo "Checking release for tag '${tag}'." + + if ! release_json="$(gh release view "${tag}" \ + --repo "${GITHUB_REPOSITORY}" \ + --json assets 2>/dev/null)"; then + echo "::error::No release is associated with tag '${tag}'." + exit 1 + fi + + has_asset="$(jq -r --arg a "${USER_GUIDE_ASSET}" \ + '[.assets[].name] | index($a) != null' <<<"${release_json}")" + if [[ "${has_asset}" != "true" ]]; then + echo "::error::Published release for tag '${tag}' does not contain asset '${USER_GUIDE_ASSET}'." + exit 1 + fi + + echo "Published release for tag '${tag}' contains '${USER_GUIDE_ASSET}'." + + - name: Download and extract the User's Manual asset + env: + GH_TOKEN: ${{ github.token }} + run: | + tag="${RELEASE_TAG}" + tmp_dir="$(mktemp -d)" + + gh release download "${tag}" \ + --repo "${GITHUB_REPOSITORY}" \ + --pattern "${USER_GUIDE_ASSET}" \ + --dir "${tmp_dir}" + + rm -rf _site + mkdir -p _site + tar -xzf "${tmp_dir}/${USER_GUIDE_ASSET}" -C _site + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: _site + + - name: Deploy User's Manual to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/upstream_integration.yml b/.github/workflows/upstream_integration.yml new file mode 100644 index 0000000..f2a1eb7 --- /dev/null +++ b/.github/workflows/upstream_integration.yml @@ -0,0 +1,291 @@ +# Copyright 2026 Elektrobit. All rights reserved. +name: Update Toolkit from Upstream Release Archive + +# Integrates an upstream EB corbos Toolkit release archive into this repository. +# The workflow downloads and unpacks the delivery archive, loads and smoke-tests +# the container images (kas build + QEMU boot of the fastdev image), opens or +# updates an integration pull request, retags and pushes the container images to +# ghcr.io, and creates/updates a draft GitHub release with the collected assets. +# +# Access control: this workflow is manually triggered (workflow_dispatch) and may +# only be run by a small set of trusted actors. The first job step aborts unless +# the triggering actor is listed in the WORKFLOW_ALLOWED_ACTORS repository +# variable (a comma-separated list of GitHub usernames). It also requires a +# privileged token input, so it must not be exposed to untrusted contributors. + +on: + workflow_dispatch: + inputs: + artifact_url: + description: "URL for the toolkit tar.gz archive" + required: true + type: string + artifactory_token: + description: "Authentication bearer token" + required: true + type: string + release_version: + description: "Release version string (e.g. v2.0.0-beta2)" + required: true + type: string + +permissions: + contents: write # create the tag + Release, upload assets + packages: write # push containers to ghcr.io + pull-requests: write # create integration PR + +# Serialize runs so overlapping executions cannot force-push over each other's +# integration branch. Do not cancel an in-progress run mid-integration. +concurrency: + group: upstream-integration + cancel-in-progress: false + +jobs: + build-from-archive: + name: Integrate Upstream Release Archive + runs-on: ubuntu-latest + timeout-minutes: 120 + env: + SCRIPTS_DIR: ${{ github.workspace }}/.github/scripts + defaults: + run: + shell: bash --noprofile --norc -euo pipefail {0} + + steps: + - name: Authorize triggering actor + env: + ALLOWED_ACTORS: ${{ vars.WORKFLOW_ALLOWED_ACTORS }} + run: | + if [[ -z "${ALLOWED_ACTORS:-}" ]]; then + echo "::error::Repository variable WORKFLOW_ALLOWED_ACTORS is not set." + echo "::error::Set it to a comma-separated list of GitHub usernames allowed to run this workflow." + exit 1 + fi + + normalized="${ALLOWED_ACTORS// /}" + if [[ ",${normalized}," != *",${GITHUB_ACTOR},"* ]]; then + echo "::error::Actor is not authorized to run this workflow." + exit 1 + fi + + echo "Actor authorization succeeded." + + - name: Verify runner prerequisites + run: | + command -v curl >/dev/null + command -v tar >/dev/null + command -v docker >/dev/null + command -v jq >/dev/null + + - name: Checkout workflow scripts + uses: actions/checkout@v4 + with: + sparse-checkout: | + .github/scripts + + - name: Download, unpack, and validate upstream delivery archive + run: | + echo "::add-mask::${{ inputs.artifact_url }}" + echo "::add-mask::${{ inputs.artifactory_token }}" + "${SCRIPTS_DIR}/download_archive.sh" \ + --url "${{ inputs.artifact_url }}" \ + --token "${{ inputs.artifactory_token }}" \ + >> "${GITHUB_ENV}" + + - name: Load container image archives into Docker + run: | + docker load -i "${DEVCONTAINER_ARCHIVE}" + docker load -i "${BUILDCONTAINER_ARCHIVE}" + + - name: "Fix BitBake execution issue on Ubuntu 23.10+" + run: | + "${BITBAKE_EXECUTION_FIXER}" --yes + + - name: "Test: Run kas build using extracted run.sh" + run: | + cd "${WORKSPACE_ROOT}" + "${RUN_SCRIPT}" -d -- kas build --target fastdev kas/public.yml + + - name: "Test: Boot fastdev image in QEMU" + run: | + bash "${SCRIPTS_DIR}/qemu_smoke_test.sh" \ + --workspace-root "${WORKSPACE_ROOT}" \ + --run-script "${RUN_SCRIPT}" \ + | grep -E '^DEV_CONTAINER_ID=[0-9a-f]{64}$' >> "${GITHUB_ENV}" + + - name: Checkout repository for integration + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: main + path: integration-repo + + - name: Create or checkout integration branch + working-directory: integration-repo + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + BRANCH="upstream-integration" + echo "INTEGRATION_BRANCH=${BRANCH}" >> "${GITHUB_ENV}" + + if git ls-remote --exit-code --heads origin "${BRANCH}" >/dev/null 2>&1; then + echo "Integration branch '${BRANCH}' already exists; checking out its state." + git fetch origin "${BRANCH}" + git checkout -B "${BRANCH}" "origin/${BRANCH}" + else + echo "Integration branch '${BRANCH}' does not exist; creating it from main." + git checkout -B "${BRANCH}" origin/main + fi + + - name: Integrate upstream workspace + working-directory: integration-repo + run: | + "${SCRIPTS_DIR}/integrate_workspace.sh" \ + --workspace-root "${WORKSPACE_ROOT}" + + - name: Commit integrated upstream workspace and push to integration branch + working-directory: integration-repo + env: + RELEASE_VERSION: ${{ inputs.release_version }} + run: | + git add -A + if git diff --cached --quiet; then + echo "No changes to commit; working copy already matches upstream." + else + git commit -m "Integrate upstream version ${RELEASE_VERSION}" + git push origin "HEAD:${INTEGRATION_BRANCH}" + fi + + - name: Create or update integration pull request + id: integration-pr + working-directory: integration-repo + env: + GH_TOKEN: ${{ github.token }} + RELEASE_VERSION: ${{ inputs.release_version }} + run: | + existing_pr="$(gh pr list --head "${INTEGRATION_BRANCH}" --base main --state open --json number --jq '.[0].number // empty')" + tag_prefix="v" + + if [[ -n "${existing_pr}" ]]; then + echo "Pull request #${existing_pr} already exists for ${INTEGRATION_BRANCH} → main." + echo "pr_tag=${tag_prefix}${existing_pr}" >> "${GITHUB_OUTPUT}" + else + pr_url="$(gh pr create \ + --head "${INTEGRATION_BRANCH}" \ + --base main \ + --title "Integrate upstream version ${RELEASE_VERSION}" \ + --body "Automated PR created by the upstream integration workflow for release ${RELEASE_VERSION}.")" + pr_number="$(echo "${pr_url}" | grep -oE '[0-9]+$')" + echo "Created pull request #${pr_number}." + echo "pr_tag=${tag_prefix}${pr_number}" >> "${GITHUB_OUTPUT}" + fi + + - name: Rewrite devcontainer image tag to pull request number + id: rewrite-tag + working-directory: integration-repo + run: | + devcontainer_json=".devcontainer/devcontainer.json" + run_sh="./scripts/run.sh" + rfi_tag="$(sed -n 's/.*"image":.*:\([^"]*\)".*/\1/p' "${devcontainer_json}" | head -1)" + + if [[ -z "${rfi_tag}" ]]; then + echo "::error::Could not extract image tag from ${devcontainer_json}." + exit 1 + fi + + echo "Original tag: ${rfi_tag}" + echo "rfi_tag=${rfi_tag}" >> "${GITHUB_OUTPUT}" + + new_tag="${{ steps.integration-pr.outputs.pr_tag }}" + sed -i "s|:${rfi_tag}\"|:${new_tag}\"|" "${devcontainer_json}" + sed -i "s|:${rfi_tag}\"|:${new_tag}\"|" "${run_sh}" + echo "Rewrote image tag to: ${new_tag}" + + git add "${devcontainer_json}" "${run_sh}" + if git diff --cached --quiet; then + echo "Tags already match; nothing to commit." + else + # Amend the previous commit to include the tag rewrite, so that the previous container tag is not left in the integration branch history. + git commit --amend --no-edit + git push --force-with-lease origin "HEAD:${INTEGRATION_BRANCH}" + fi + + - name: Update container tags and push to registry + working-directory: integration-repo + run: | + echo "${{ github.token }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin + + new_tag="${{ steps.integration-pr.outputs.pr_tag }}" + docker tag "${DEVCONTAINER_IMAGE}:${{ steps.rewrite-tag.outputs.rfi_tag }}" "${DEVCONTAINER_IMAGE}:${new_tag}" + docker tag "${BUILDCONTAINER_IMAGE}:${{ steps.rewrite-tag.outputs.rfi_tag }}" "${BUILDCONTAINER_IMAGE}:${new_tag}" + + echo "Pushing updated container images to registry..." + docker push "${DEVCONTAINER_IMAGE}:${new_tag}" + docker push "${BUILDCONTAINER_IMAGE}:${new_tag}" + + - name: Package release assets + run: | + bash "${SCRIPTS_DIR}/package_release_assets.sh" \ + --tmp-work-dir "${TMP_WORK_DIR}" \ + --archive-path "${ARCHIVE_PATH}" \ + --manual-pdf "${MANUAL_PDF}" \ + --manual-html-dir "${MANUAL_HTML_DIR}" \ + --prebuilt-dir "${PREBUILT_DIR}" \ + >> "${GITHUB_ENV}" + + - name: Create or update draft release and upload assets + working-directory: integration-repo + env: + GH_TOKEN: ${{ github.token }} + RELEASE_VERSION: ${{ inputs.release_version }} + run: | + shopt -s nullglob + assets=( "${RELEASE_ASSETS_DIR}"/* ) + shopt -u nullglob + + if [[ ${#assets[@]} -eq 0 ]]; then + echo "::error::No release assets found in ${RELEASE_ASSETS_DIR}." + exit 1 + fi + + if gh release view "${RELEASE_VERSION}" >/dev/null 2>&1; then + is_draft="$(gh release view "${RELEASE_VERSION}" --json isDraft --jq '.isDraft')" + if [[ "${is_draft}" != "true" ]]; then + echo "::error::Release '${RELEASE_VERSION}' already exists and is published (not a draft); refusing to modify it." + exit 1 + fi + echo "Draft release '${RELEASE_VERSION}' already exists; updating assets." + gh release upload "${RELEASE_VERSION}" "${assets[@]}" --clobber + else + echo "Creating draft release '${RELEASE_VERSION}'." + # A draft release with a not-yet-existing tag does not create the tag + # or point at any commit/branch until it is published. + gh release create "${RELEASE_VERSION}" \ + --draft \ + --title "${RELEASE_VERSION}" \ + "${assets[@]}" + fi + + - name: Clean up runner resources + if: always() + run: | + set +e + + # Stop and remove the detached devcontainer, if it was started. + if [[ -n "${DEV_CONTAINER_ID:-}" ]]; then + echo "Removing devcontainer ${DEV_CONTAINER_ID}" + docker rm -f "${DEV_CONTAINER_ID}" + fi + + # Kill any lingering QEMU processes started by the smoke test. + pkill -f qemu-system + + # Drop registry credentials written by docker login. + docker logout ghcr.io + + # Remove the temporary working directory used for download/extraction. + if [[ -n "${TMP_WORK_DIR:-}" && -d "${TMP_WORK_DIR}" ]]; then + echo "Removing temporary work directory ${TMP_WORK_DIR}" + sudo rm -rf -- "${TMP_WORK_DIR}" + fi diff --git a/README.md b/README.md index 8ed8c99..581ee46 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,25 @@ You can explore this car software platform and develop applications, build image ## Getting Started -Detailed documentation, including setup instructions and user guides, is available as part of the release assets. -Check the [Releases](../../releases) page and download the archive attached to the latest release. -Currently, this is [`eb_corbos_toolkit_2.0.0-beta1.tar.gz`](https://github.com/Elektrobit/eb_corbos_toolkit/releases/download/v2.0.0-beta1/eb_corbos_toolkit_2.0.0-beta1.tar.gz). +Detailed documentation, including setup instructions and a User's Manual, is available as part of the GitHub Release Assets. +The [latest User's Manual](https://elektrobit.github.io/eb_corbos_toolkit/) is also available via GitHub Pages. +Check the [GitHub Release](../../releases) page and download any required asset from the latest release. + +You have two options to start using the EB corbos Toolkit. + +### Start with Self-Contained Delivery Archive + +Open the User's Manual and follow the instructions from the start. +The `DELIVERY_TARBALL_URL` [mentioned in the User's Manual](https://elektrobit.github.io/eb_corbos_toolkit/#_obtain_eb_corbos_toolkit) must be the URL pointing to the `eb_corbos_toolkit.tar.gz` Asset of the latest [GitHub Release](../../releases). + +### Start with GitHub Repository + +Clone the repository and open it in a [devcontainer](https://containers.dev/)-capable IDE, e.g [Visual Studio Code](https://code.visualstudio.com/). +Re-open it in the devcontainer. +Open the [User's Manual](https://elektrobit.github.io/eb_corbos_toolkit/) and follow the instructions, starting with the section [Rebuild and Run Target Image](https://elektrobit.github.io/eb_corbos_toolkit/#_rebuild_and_run_target_image). + +> [!NOTE] +> The instructions "Obtain EB corbos Toolkit", "Enter Development Container", and "Run Prebuilt Target Image" only work with the self-contained delivery archive. ## Reporting Issues