From d2a6f8d1957625ec36c7af69838a6a72f26278a0 Mon Sep 17 00:00:00 2001 From: Marc Plano-Lesay Date: Mon, 24 Nov 2025 15:14:51 +1100 Subject: [PATCH] Init --- .forgejo/workflows/build.yml | 101 ++++++++++++++ .../workflows/renovate-config-validation.yaml | 24 ++++ .forgejo/workflows/yamllint.yaml | 18 +++ .yamllint.yaml | 30 ++++ Dockerfile | 128 ++++++++++++++++++ README.md | 57 +++++++- renovate.json | 47 +++++++ 7 files changed, 403 insertions(+), 2 deletions(-) create mode 100644 .forgejo/workflows/build.yml create mode 100644 .forgejo/workflows/renovate-config-validation.yaml create mode 100644 .forgejo/workflows/yamllint.yaml create mode 100644 .yamllint.yaml create mode 100644 Dockerfile create mode 100644 renovate.json diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml new file mode 100644 index 0000000..266dc4f --- /dev/null +++ b/.forgejo/workflows/build.yml @@ -0,0 +1,101 @@ +--- +# yamllint disable rule:line-length +name: Build talosctl+talhelper+sops image (push on main only) + +on: # yamllint disable-line rule:truthy + push: + branches: ["**"] + workflow_dispatch: {} + +env: + # Configure these in your repository Settings → Variables/Secrets + REGISTRY: ${{ vars.REGISTRY }} + # e.g. forgejo.example.com/owner/talos-tools + IMAGE_NAME: ${{ vars.IMAGE_NAME }} + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Extract component versions from Dockerfile + id: versions + run: | + set -euo pipefail + TALOSCTL_VERSION=$(grep -E "^ARG TALOSCTL_VERSION=" Dockerfile | head -n1 | cut -d'=' -f2) + TALHELPER_VERSION=$(grep -E "^ARG TALHELPER_VERSION=" Dockerfile | head -n1 | cut -d'=' -f2) + SOPS_VERSION=$(grep -E "^ARG SOPS_VERSION=" Dockerfile | head -n1 | cut -d'=' -f2) + echo "talosctl=${TALOSCTL_VERSION}" >> "$GITHUB_OUTPUT" + echo "talhelper=${TALHELPER_VERSION}" >> "$GITHUB_OUTPUT" + echo "sops=${SOPS_VERSION}" >> "$GITHUB_OUTPUT" + TAG="v${TALOSCTL_VERSION}-${TALHELPER_VERSION}-${SOPS_VERSION}" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to registry (main only) + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Build and push image (main only) + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + provenance: false + cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache + cache-to: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache,mode=max + build-args: | + TALOSCTL_VERSION=${{ steps.versions.outputs.talosctl }} + TALHELPER_VERSION=${{ steps.versions.outputs.talhelper }} + SOPS_VERSION=${{ steps.versions.outputs.sops }} + tags: | + ${{ env.IMAGE_NAME }}:${{ steps.versions.outputs.tag }} + ${{ env.IMAGE_NAME }}:latest + labels: | + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} + + - name: Build without push (branches other than main) + if: github.ref != 'refs/heads/main' && github.ref != 'refs/heads/master' + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64 + load: true + push: false + provenance: false + build-args: | + TALOSCTL_VERSION=${{ steps.versions.outputs.talosctl }} + TALHELPER_VERSION=${{ steps.versions.outputs.talhelper }} + SOPS_VERSION=${{ steps.versions.outputs.sops }} + tags: | + ${{ env.IMAGE_NAME }}:${{ steps.versions.outputs.tag }} + labels: | + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} + + - name: "Smoke test (main: run pushed image)" + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' + run: | + docker run --rm ${{ env.IMAGE_NAME }}:${{ steps.versions.outputs.tag }} sh -lc \ + 'talosctl version --client && talhelper --version && sops --version' + + - name: "Smoke test (branch: run locally loaded image)" + if: github.ref != 'refs/heads/main' && github.ref != 'refs/heads/master' + run: | + docker run --rm ${{ env.IMAGE_NAME }}:${{ steps.versions.outputs.tag }} sh -lc \ + 'talosctl version --client && talhelper --version && sops --version' +... diff --git a/.forgejo/workflows/renovate-config-validation.yaml b/.forgejo/workflows/renovate-config-validation.yaml new file mode 100644 index 0000000..96a34e1 --- /dev/null +++ b/.forgejo/workflows/renovate-config-validation.yaml @@ -0,0 +1,24 @@ +--- +name: Checking Renovate configuration + +on: # yamllint disable-line rule:truthy + pull_request: + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Cache npm (renovate) + uses: actions/cache@v4 + with: + path: ~/.npm + key: ${{ runner.os }}-npm-renovate + restore-keys: | + ${{ runner.os }}-npm-renovate- + - name: Validate Renovate configuration + uses: suzuki-shunsuke/github-action-renovate-config-validator@v1.1.1 + env: + NPM_CONFIG_CACHE: ~/.npm +... diff --git a/.forgejo/workflows/yamllint.yaml b/.forgejo/workflows/yamllint.yaml new file mode 100644 index 0000000..1b5cb26 --- /dev/null +++ b/.forgejo/workflows/yamllint.yaml @@ -0,0 +1,18 @@ +--- +name: Checking yaml + +on: # yamllint disable-line rule:truthy + pull_request: + +jobs: + yamllint: + name: Run yamllint + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Run YAML linter + uses: bewuethr/yamllint-action@v1 + with: + config-file: .yamllint.yaml +... diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..79bc82f --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,30 @@ +--- +ignore: | + .platformio + secrets.yaml + +rules: + braces: enable + brackets: enable + colons: enable + commas: enable + comments: enable + comments-indentation: enable + document-end: enable + document-start: enable + empty-lines: + max: 1 + empty-values: disable + hyphens: enable + indentation: enable + key-duplicates: enable + key-ordering: disable + line-length: + max: 100 + new-line-at-end-of-file: enable + new-lines: enable + octal-values: enable + quoted-strings: disable + trailing-spaces: enable + truthy: enable +... diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..618c726 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,128 @@ +# syntax=docker/dockerfile:1.7 + +# Build an OCI image that provides: +# - talosctl +# - talhelper +# - sops +# +# Versions are controlled by build ARGs below. Renovate is configured to bump +# these ARGs automatically. The CI workflow builds the image and tags it with a +# composite version tag reflecting all three component versions. + +# renovate: datasource=github-releases depName=siderolabs/talos versioning=semver +ARG TALOSCTL_VERSION=1.9.2 +# renovate: datasource=github-releases depName=budimanjojo/talhelper versioning=semver +ARG TALHELPER_VERSION=3.0.39 +# renovate: datasource=github-releases depName=getsops/sops versioning=semver +ARG SOPS_VERSION=3.11.0 + +# renovate: datasource=docker depName=cgr.dev/chainguard/wolfi-base +FROM cgr.dev/chainguard/wolfi-base:latest AS downloader + +ARG TALOSCTL_VERSION +ARG TALHELPER_VERSION +ARG SOPS_VERSION + +RUN set -eux; \ + apk add --no-cache curl ca-certificates-bundle + +# Map Docker TARGETARCH to upstream asset architecture naming where needed. +ARG TARGETARCH +RUN set -eux; \ + case "${TARGETARCH}" in \ + amd64) TALOS_ARCH=amd64; TALHELPER_ARCH=amd64; SOPS_ARCH=amd64 ;; \ + arm64) TALOS_ARCH=arm64; TALHELPER_ARCH=arm64; SOPS_ARCH=arm64 ;; \ + *) echo "Unsupported TARGETARCH=${TARGETARCH}"; exit 1 ;; \ + esac; \ + echo "TALOS_ARCH=${TALOS_ARCH}" > /tmp/arches.env; \ + echo "TALHELPER_ARCH=${TALHELPER_ARCH}" >> /tmp/arches.env; \ + echo "SOPS_ARCH=${SOPS_ARCH}" >> /tmp/arches.env + +SHELL ["/bin/sh", "-c"] + +# Download talosctl and verify checksum +RUN . /tmp/arches.env; \ + set -eux; \ + TALOS_URL="https://github.com/siderolabs/talos/releases/download/v${TALOSCTL_VERSION}/talosctl-linux-${TALOS_ARCH}"; \ + curl -fsSL -o /tmp/talosctl "${TALOS_URL}"; \ + chmod +x /tmp/talosctl; \ + if curl -fsSL -o /tmp/talosctl.sha256 "${TALOS_URL}.sha256"; then \ + TALOS_SHA=$(tr -d ' \n\r' < /tmp/talosctl.sha256); \ + else \ + curl -fsSL -o /tmp/talos_checksums.txt "https://github.com/siderolabs/talos/releases/download/v${TALOSCTL_VERSION}/sha256sum.txt"; \ + TALOS_SHA=$(grep "$(basename ${TALOS_URL})" /tmp/talos_checksums.txt | awk '{print $1}' | tr -d ' \n\r'); \ + fi; \ + echo "${TALOS_SHA} /tmp/talosctl" | sha256sum -c -; \ + echo "${TALOS_URL}" > /tmp/talosctl.src; \ + echo "${TALOS_SHA}" > /tmp/talosctl.sha + +# Download talhelper (tar.gz containing the binary) and verify checksum +RUN . /tmp/arches.env; \ + set -eux; \ + TALHELPER_TGZ_URL="https://github.com/budimanjojo/talhelper/releases/download/v${TALHELPER_VERSION}/talhelper_linux_${TALHELPER_ARCH}.tar.gz"; \ + curl -fsSL -o /tmp/talhelper.tgz "${TALHELPER_TGZ_URL}"; \ + if curl -fsSL -o /tmp/talhelper.tgz.sha256 "${TALHELPER_TGZ_URL}.sha256"; then \ + TALHELPER_TGZ_SHA=$(tr -d ' \n\r' < /tmp/talhelper.tgz.sha256); \ + else \ + curl -fsSL -o /tmp/talhelper_checksums.txt "https://github.com/budimanjojo/talhelper/releases/download/v${TALHELPER_VERSION}/checksums.txt"; \ + TALHELPER_TGZ_SHA=$(grep "$(basename ${TALHELPER_TGZ_URL})" /tmp/talhelper_checksums.txt | awk '{print $1}' | tr -d ' \n\r'); \ + fi; \ + echo "${TALHELPER_TGZ_SHA} /tmp/talhelper.tgz" | sha256sum -c -; \ + mkdir -p /tmp/talhelper && tar -xzf /tmp/talhelper.tgz -C /tmp/talhelper; \ + mv /tmp/talhelper/talhelper /tmp/talhelper.bin; \ + chmod +x /tmp/talhelper.bin; \ + echo "${TALHELPER_TGZ_URL}" > /tmp/talhelper.src; \ + echo "${TALHELPER_TGZ_SHA}" > /tmp/talhelper.sha + +# Download sops and verify checksum +RUN . /tmp/arches.env; \ + set -eux; \ + SOPS_URL="https://github.com/getsops/sops/releases/download/v${SOPS_VERSION}/sops-v${SOPS_VERSION}.linux.${SOPS_ARCH}"; \ + curl -fsSL -o /tmp/sops "${SOPS_URL}"; \ + chmod +x /tmp/sops; \ + if curl -fsSL -o /tmp/sops.sha256 "${SOPS_URL}.sha256"; then \ + SOPS_SHA=$(tr -d ' \n\r' < /tmp/sops.sha256); \ + else \ + curl -fsSL -o /tmp/sops_checksums.txt "https://github.com/getsops/sops/releases/download/v${SOPS_VERSION}/sops-v${SOPS_VERSION}.checksums.txt"; \ + SOPS_SHA=$(grep "$(basename ${SOPS_URL})" /tmp/sops_checksums.txt | awk '{print $1}' | tr -d ' \n\r'); \ + fi; \ + echo "${SOPS_SHA} /tmp/sops" | sha256sum -c -; \ + echo "${SOPS_URL}" > /tmp/sops.src; \ + echo "${SOPS_SHA}" > /tmp/sops.sha + + +# renovate: datasource=docker depName=cgr.dev/chainguard/wolfi-base +FROM cgr.dev/chainguard/wolfi-base:latest + +ARG TALOSCTL_VERSION +ARG TALHELPER_VERSION +ARG SOPS_VERSION + +LABEL org.opencontainers.image.title="talosctl + talhelper + sops" +LABEL org.opencontainers.image.description="Utility image containing talosctl, talhelper, and sops" +LABEL org.opencontainers.image.source="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" +LABEL org.opencontainers.image.licenses="MIT" +LABEL org.opencontainers.image.version.talosctl="${TALOSCTL_VERSION}" +LABEL org.opencontainers.image.version.talhelper="${TALHELPER_VERSION}" +LABEL org.opencontainers.image.version.sops="${SOPS_VERSION}" +LABEL org.opencontainers.image.url.talosctl="https://github.com/siderolabs/talos" +LABEL org.opencontainers.image.url.talhelper="https://github.com/budimanjojo/talhelper" +LABEL org.opencontainers.image.url.sops="https://github.com/getsops/sops" + +RUN set -eux; \ + apk add --no-cache ca-certificates-bundle bash git openssh-client; \ + mkdir -p /usr/local/share/checksums + +COPY --from=downloader /tmp/talosctl /usr/local/bin/talosctl +COPY --from=downloader /tmp/talhelper.bin /usr/local/bin/talhelper +COPY --from=downloader /tmp/sops /usr/local/bin/sops +COPY --from=downloader /tmp/*.sha /usr/local/share/checksums/ +COPY --from=downloader /tmp/*.src /usr/local/share/checksums/ + +RUN set -eux; \ + chmod +x /usr/local/bin/talosctl /usr/local/bin/talhelper /usr/local/bin/sops + +ENV PAGER=cat + +# Print versions by default so users can see what's inside quickly. +CMD talosctl version --client && talhelper --version && sops --version diff --git a/README.md b/README.md index 22c005e..9d52e7a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,56 @@ -# talos +# talos tools image -talosctl + talhelper +sops image \ No newline at end of file +An OCI image (Wolfi-based) containing: +- talosctl +- talhelper +- sops + +The image is built on Wolfi to keep size and surface area minimal, then built via Forgejo Actions. On pushes to `main` (or `master`), the image is pushed to your Forgejo container registry. On other branches, the workflow builds the image but does not push it (to validate PRs). The published tag encodes the versions of all three tools: +- Tag format: `v--` (for example: `v1.9.2-3.0.39-3.11.0`), plus `latest`. + +Contents are defined in `Dockerfile`. Versions are pinned via build `ARG`s so they can be updated automatically by Renovate. +Additionally, the build verifies SHA256 checksums for all downloaded binaries and includes their source URLs and checksums inside the image at `/usr/local/share/checksums/`. + +Setup +1) Configure Forgejo variables and secrets +- Repository Variables (Settings → Variables): + - `REGISTRY`: the registry hostname, e.g. `forgejo.example.com` + - `IMAGE_NAME`: full image name including registry and path, e.g. `forgejo.example.com/owner/talos-tools` +- Repository Secrets (Settings → Secrets): + - `REGISTRY_USERNAME`: username with push permission to the registry + - `REGISTRY_PASSWORD`: the password or token + +2) Enable the workflow +The workflow file `.forgejo/workflows/build.yml` runs on any branch push and on manual dispatch. +- On `main`/`master`: it builds a multi-arch image (linux/amd64, linux/arm64) and pushes it, using a registry-backed Buildx cache (no extra infra needed). +- On other branches: it builds the image (no push) to ensure the change is buildable before merging. + +3) Renovate configuration +`renovate.json` configures Renovate to track GitHub releases and update the version `ARG`s in the `Dockerfile` for: +- `siderolabs/talos` (talosctl) +- `budimanjojo/talhelper` +- `getsops/sops` + +When Renovate opens a PR and it is merged, the workflow will build and push a new image. The tag is computed from the three versions in the `Dockerfile`. + +Local build +To build locally (example versions): +``` +docker build \ + --build-arg TALOSCTL_VERSION=1.9.2 \ + --build-arg TALHELPER_VERSION=3.0.39 \ + --build-arg SOPS_VERSION=3.11.0 \ + -t talos-tools:dev . +``` + +Image usage +``` +docker run --rm -it your.registry/owner/talos-tools:v1.9.2-3.0.39-3.11.0 talosctl version --client +``` + +Notes +- The workflow uses Docker Buildx and QEMU to produce multi-arch images. +- The Dockerfile maps architectures to the upstream asset names as required (e.g., talhelper uses `amd64` and `arm64` asset names). + - Base image: `cgr.dev/chainguard/wolfi-base:latest` for a smaller footprint than Alpine. Renovate is enabled to track and pin the base image digest automatically. + - Runtime shell: `bash` is included because this image is intended to be used in Forgejo Actions jobs that require a shell. + - Integrity: SHA256 checksum verification is performed during the image build for `talosctl`, `talhelper` (tarball), and `sops`. \ No newline at end of file diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..84788a4 --- /dev/null +++ b/renovate.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "assignees": ["kernald"], + "extends": [ + "config:recommended" + ], + "enabledManagers": ["custom.regex", "dockerfile"], + "pinDigests": true, + "customManagers": [ + { + "customType": "regex", + "fileMatch": ["^Dockerfile$"], + "matchStrings": [ + "#\\s*renovate:\\s*datasource=(?[^\\s]+)\\s+depName=(?[^\\s]+)\\s+versioning=(?[^\\s]+)\\nARG\\s+TALOSCTL_VERSION=(?[^\\n]+)" + ], + "datasourceTemplate": "{{{datasource}}}", + "depNameTemplate": "{{{depName}}}", + "versioningTemplate": "{{{versioning}}}", + "extractVersionTemplate": "^v?(?.*)$", + "autoReplaceStringTemplate": "ARG TALOSCTL_VERSION={{newValue}}" + }, + { + "customType": "regex", + "fileMatch": ["^Dockerfile$"], + "matchStrings": [ + "#\\s*renovate:\\s*datasource=(?[^\\s]+)\\s+depName=(?[^\\s]+)\\s+versioning=(?[^\\s]+)\\nARG\\s+TALHELPER_VERSION=(?[^\\n]+)" + ], + "datasourceTemplate": "{{{datasource}}}", + "depNameTemplate": "{{{depName}}}", + "versioningTemplate": "{{{versioning}}}", + "extractVersionTemplate": "^v?(?.*)$", + "autoReplaceStringTemplate": "ARG TALHELPER_VERSION={{newValue}}" + }, + { + "customType": "regex", + "fileMatch": ["^Dockerfile$"], + "matchStrings": [ + "#\\s*renovate:\\s*datasource=(?[^\\s]+)\\s+depName=(?[^\\s]+)\\s+versioning=(?[^\\s]+)\\nARG\\s+SOPS_VERSION=(?[^\\n]+)" + ], + "datasourceTemplate": "{{{datasource}}}", + "depNameTemplate": "{{{depName}}}", + "versioningTemplate": "{{{versioning}}}", + "extractVersionTemplate": "^v?(?.*)$", + "autoReplaceStringTemplate": "ARG SOPS_VERSION={{newValue}}" + } + ] +}