diff --git a/.github/workflows/build-rugpi-bakery.yml b/.github/workflows/build-rugpi-bakery.yml deleted file mode 100644 index 64f2243..0000000 --- a/.github/workflows/build-rugpi-bakery.yml +++ /dev/null @@ -1,138 +0,0 @@ -name: Build Rugpi Bakery - -on: - schedule: - # This creates the nightly version of Rugpi Bakery. - - cron: '0 0 * * *' - push: - branches: - - '*' - tags: - - 'v*' - pull_request: - workflow_dispatch: - -env: - REGISTRY: ghcr.io - IMAGE_NAME: "ghcr.io/silitics/rugpi-bakery" - -jobs: - metadata: - name: Docker Image Metedata - runs-on: ubuntu-latest - outputs: - labels: ${{ steps.meta.outputs.labels }} - json: ${{ steps.meta.outputs.json }} - version: ${{ steps.meta.outputs.version }} - steps: - - name: Docker Meta - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.IMAGE_NAME }} - # Include this once we released version 1.0.0. - # type=semver,pattern=v{{major}} - tags: | - type=schedule,pattern=nightly - type=schedule,pattern=nightly-{{date 'YYYYMMDD'}} - type=semver,pattern=v{{major}}.{{minor}}.{{patch}} - type=semver,pattern=v{{major}}.{{minor}} - type=ref,event=branch - type=ref,event=pr - labels: | - org.opencontainers.image.title=Rugpi Bakery - org.opencontainers.image.vendor=Silitics GmbH - - build-platform-images: - name: Build Docker Images - runs-on: ubuntu-latest - needs: [metadata] - strategy: - matrix: - platform: [linux/amd64, linux/arm64] - steps: - - name: Checkout Repository - uses: actions/checkout@v3 - with: - lfs: true - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and Push Image - id: build - uses: docker/build-push-action@v5 - with: - context: . - file: bakery/Dockerfile - platforms: ${{ matrix.platform }} - labels: ${{ needs.metadata.outputs.labels }} - push: true - tags: ${{ env.IMAGE_NAME }} - cache-from: type=gha - cache-to: type=gha,mode=max - outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true - build-args: | - BUILDTIME=${{ fromJSON(needs.metadata.outputs.json).labels['org.opencontainers.image.created'] }} - VERSION=${{ fromJSON(needs.metadata.outputs.json).labels['org.opencontainers.image.version'] }} - REVISION=${{ fromJSON(needs.metadata.outputs.json).labels['org.opencontainers.image.revision'] }} - - - name: Export Digest - run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" - - - name: Upload Digest - uses: actions/upload-artifact@v3 - with: - name: digests - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - - build-and-push: - name: Build and Push Docker Image - runs-on: ubuntu-latest - needs: - - metadata - - build-platform-images - steps: - - name: Download Digests - uses: actions/download-artifact@v3 - with: - name: digests - path: /tmp/digests - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Create Manifest List and Push - working-directory: /tmp/digests - env: - # We use an environment variable here because the shell knows how to properly escape JSON. - METADATA_JSON: ${{ needs.metadata.outputs.json }} - run: | - docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$METADATA_JSON") \ - $(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *) - - - name: Inspect Image - run: | - docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ needs.metadata.outputs.version }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..0831ebe --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,236 @@ +name: Build + +on: + schedule: + # This builds the nightly version of Rugpi. + - cron: '0 0 * * *' + push: + branches: + - '*' + tags: + - 'v*' + pull_request: + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: "ghcr.io/silitics/rugpi-bakery" + +jobs: + build_binaries: + name: "Build Binaries (${{ matrix.target }})" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: + - aarch64-unknown-linux-musl + - armv7-unknown-linux-musleabihf + - arm-unknown-linux-musleabihf + - arm-unknown-linux-musleabi + - x86_64-unknown-linux-musl + - i686-unknown-linux-musl + - riscv64gc-unknown-linux-gnu + env: + CROSS_VERSION: "0.2.5" + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Cross + run: | + wget "https://github.com/cross-rs/cross/releases/download/v${{ env.CROSS_VERSION }}/cross-x86_64-unknown-linux-musl.tar.gz" + tar -xvf cross-x86_64-unknown-linux-musl.tar.gz + - name: Build Binaries + run: | + ./cross build --release --target ${{ matrix.target }} + - name: Create Artifact + run: | + cd target/${{ matrix.target }}/release + tar -cf ../../../${{ matrix.target }}.tar rugpi-*[!d] + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: binaries-${{ matrix.target }} + path: ${{ matrix.target }}.tar + if-no-files-found: error + + bakery_metadata: + name: Bakery Image Metedata + runs-on: ubuntu-latest + outputs: + labels: ${{ steps.meta.outputs.labels }} + json: ${{ steps.meta.outputs.json }} + version: ${{ steps.meta.outputs.version }} + steps: + - name: Docker Meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_NAME }} + # Include this once we released version 1.0.0. + # type=semver,pattern=v{{major}} + tags: | + type=schedule,pattern=nightly + type=schedule,pattern=nightly-{{date 'YYYYMMDD'}} + type=semver,pattern=v{{major}}.{{minor}}.{{patch}} + type=semver,pattern=v{{major}}.{{minor}} + type=ref,event=branch + type=ref,event=pr + type=sha,prefix=git- + labels: | + org.opencontainers.image.title=Rugpi Bakery + org.opencontainers.image.vendor=Silitics GmbH + + build_bakery_images: + name: "Build Bakery Image (${{ matrix.arch }})" + runs-on: ubuntu-latest + needs: + - bakery_metadata + - build_binaries + strategy: + matrix: + arch: + - amd64 + - arm64 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + lfs: true + - name: Download Binaries + uses: actions/download-artifact@v4 + with: + pattern: binaries-* + path: build/binaries + merge-multiple: true + - name: Extract Binaries + shell: bash + run: | + set -euo pipefail + cd build/binaries + for tar_file in *.tar; do + if [ -f "${tar_file}" ]; then + target_name="${tar_file%.tar}" + mkdir "$target_name" + tar -xf "$tar_file" -C "$target_name" + rm -f "$tar_file" + fi + done + find . + - name: Set up QEMU + if: ${{ matrix.arch != 'amd64' }} + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and Push Image + id: build + uses: docker/build-push-action@v5 + with: + context: . + file: bakery/Dockerfile + platforms: linux/${{ matrix.arch }} + labels: ${{ needs.bakery_metadata.outputs.labels }} + push: true + tags: ${{ env.IMAGE_NAME }} + cache-from: type=gha + cache-to: type=gha,mode=max + outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true + build-args: | + BUILDTIME=${{ fromJSON(needs.bakery_metadata.outputs.json).labels['org.opencontainers.image.created'] }} + VERSION=${{ fromJSON(needs.bakery_metadata.outputs.json).labels['org.opencontainers.image.version'] }} + REVISION=${{ fromJSON(needs.bakery_metadata.outputs.json).labels['org.opencontainers.image.revision'] }} + - name: Export Digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + - name: Upload Digest + uses: actions/upload-artifact@v4 + with: + name: bakery-digest-${{ matrix.arch }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 2 + + build_bakery_image: + name: Build and Push Bakery Image + runs-on: ubuntu-latest + needs: + - bakery_metadata + - build_bakery_images + outputs: + digest: ${{ steps.digest.outputs.digest }} + steps: + - name: Download Digests + uses: actions/download-artifact@v4 + with: + pattern: bakery-digest-* + path: /tmp/digests + merge-multiple: true + - name: List Digests + run: | + ls -l /tmp/digests + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Create Manifest List and Push + shell: bash + working-directory: /tmp/digests + env: + # We use an environment variable here because the shell knows how to properly escape JSON. + METADATA_JSON: ${{ needs.bakery_metadata.outputs.json }} + run: | + docker buildx imagetools create \ + $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$METADATA_JSON") \ + --annotation "index:org.opencontainers.image.title=Rugpi Bakery" \ + --annotation "index:org.opencontainers.image.vendor=Silitics GmbH" \ + $(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *) + - name: Inspect Image + run: | + docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ needs.bakery_metadata.outputs.version }} + - name: Extract Digest + id: digest + run: | + docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ needs.bakery_metadata.outputs.version }} \ + --format "{{json .Manifest}}" \ + | jq '.digest' > digest.txt + DIGEST=$(cat digest.txt) + echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT" + + # attest_bakery_image: + # name: Attest Bakery Image + # runs-on: ubuntu-latest + # needs: + # - build_bakery_image + # permissions: + # id-token: write + # packages: write + # contents: read + # attestations: write + # steps: + # - name: Checkout + # uses: actions/checkout@v4 + # - name: Login to GitHub Container Registry + # uses: docker/login-action@v3 + # with: + # registry: ${{ env.REGISTRY }} + # username: ${{ github.actor }} + # password: ${{ secrets.GITHUB_TOKEN }} + # - name: Attest Image + # uses: actions/attest-build-provenance@v1 + # id: attest + # with: + # subject-name: ${{ env.IMAGE_NAME }} + # subject-digest: ${{ needs.build_bakery_image.outputs.digest }} + # push-to-registry: true diff --git a/.gitignore b/.gitignore index ffffe6b..18283ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .rugpi /.vscode /target -/playground \ No newline at end of file +/playground +/build \ No newline at end of file diff --git a/bakery/Dockerfile b/bakery/Dockerfile index ad2ac22..219b0d0 100644 --- a/bakery/Dockerfile +++ b/bakery/Dockerfile @@ -63,6 +63,8 @@ RUN /tmp/rugpi-docker/10-build.sh ######################################################################################### FROM debian:latest AS bakery +ARG TARGETPLATFORM + COPY bakery/layers/bakery/00-base.sh /tmp/rugpi-docker/00-base.sh RUN /tmp/rugpi-docker/00-base.sh @@ -79,13 +81,17 @@ COPY boot /usr/share/rugpi/boot COPY bakery/layers/bakery/10-setup.sh /tmp/rugpi-docker/10-setup.sh RUN /tmp/rugpi-docker/10-setup.sh -RUN mkdir -p /usr/share/rugpi/binaries/{arm64,armhf} -COPY --from=builder /project/target/aarch64-unknown-linux-musl/release/rugpi-ctrl /usr/share/rugpi/binaries/arm64/ -COPY --from=builder /project/target/aarch64-unknown-linux-musl/release/rugpi-admin /usr/share/rugpi/binaries/arm64/ -COPY --from=builder /project/target/arm-unknown-linux-musleabihf/release/rugpi-ctrl /usr/share/rugpi/binaries/armhf/ -COPY --from=builder /project/target/arm-unknown-linux-musleabihf/release/rugpi-admin /usr/share/rugpi/binaries/armhf/ +COPY build/binaries /usr/share/rugpi/binaries WORKDIR /project -COPY --from=builder /project/target/release/rugpi-bakery /usr/local/bin +RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ + cp /usr/share/rugpi/binaries/x86_64-unknown-linux-musl/rugpi-bakery /usr/local/bin; \ + elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + cp /usr/share/rugpi/binaries/aarch64-unknown-linux-musl/rugpi-bakery /usr/local/bin; \ + else \ + echo "Unsupported platform ${TARGETPLATFORM}."; \ + exit 1; \ + fi + ENTRYPOINT ["/usr/local/bin/rugpi-bakery"] \ No newline at end of file diff --git a/bakery/repositories/core/recipes/rugpi-ctrl/steps/01-run.sh b/bakery/repositories/core/recipes/rugpi-ctrl/steps/01-run.sh index b4bbefb..68d5997 100755 --- a/bakery/repositories/core/recipes/rugpi-ctrl/steps/01-run.sh +++ b/bakery/repositories/core/recipes/rugpi-ctrl/steps/01-run.sh @@ -2,4 +2,17 @@ set -euo pipefail -cp -rp "/usr/share/rugpi/binaries/${RUGPI_ARCH}/"* "${RUGPI_ROOT_DIR}/usr/bin" +case "${RUGPI_ARCH}" in + "armhf") + TARGET="arm-unknown-linux-musleabihf" + ;; + "arm64") + TARGET="aarch64-unknown-linux-musl" + ;; +esac + +cp "/usr/share/rugpi/binaries/${TARGET}/rugpi-ctrl" "${RUGPI_ROOT_DIR}/usr/bin" + +if [ "${RECIPE_PARAM_RUGPI_ADMIN}" = "true" ]; then + cp "/usr/share/rugpi/binaries/${TARGET}/rugpi-admin" "${RUGPI_ROOT_DIR}/usr/bin" +fi diff --git a/xtask/src/main.rs b/xtask/src/main.rs index f2e0ead..90a3e17 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -13,6 +13,7 @@ pub struct Args { pub enum Task { Doc, BuildImage, + BuildBinaries { target: Option }, } pub fn project_path() -> PathBuf { @@ -49,6 +50,35 @@ fn main() -> anyhow::Result<()> { .with_stderr(Out::Inherit) )?; } + Task::BuildBinaries { target } => { + let target = target.as_deref().unwrap_or("aarch64-unknown-linux-musl"); + run!( + env, + ["cargo", "build", "--release", "--target", target] + .with_stdout(Out::Inherit) + .with_stderr(Out::Inherit) + )?; + let binaries_dir = project_path().join("build/binaries").join(target); + if binaries_dir.exists() { + std::fs::remove_dir_all(&binaries_dir)?; + } + std::fs::create_dir_all(&binaries_dir)?; + let target_dir = project_path().join("target").join(target).join("release"); + for entry in std::fs::read_dir(&target_dir)? { + let entry = entry?; + let file_type = entry.file_type()?; + if !file_type.is_file() { + continue; + } + let Ok(file_name) = entry.file_name().into_string() else { + continue; + }; + if !file_name.starts_with("rugpi-") || file_name.ends_with(".d") { + continue; + } + std::fs::hard_link(entry.path(), binaries_dir.join(file_name))?; + } + } } Ok(()) }