diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..146f6ba --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,48 @@ +name: Build Alpine Packages + +on: + push: + branches: + - master + - main + tags: + - "*" + pull_request: + workflow_dispatch: + +jobs: + build-and-publish: + runs-on: ubuntu-latest + container: + image: alpine:3.23 + env: + ALPINE_VERSION: "3.23" + ALPINE_ARCHES: "x86_64 aarch64" + ALPINE_REGISTRY_BRANCH: "v3.23" + ALPINE_REGISTRY_REPOSITORY: "${{ vars.PACKAGE_NAME }}" + INSTANCE_URL: "${{ vars.INSTANCE_URL }}" + PACKAGE_OWNER: "${{ vars.PACKAGE_OWNER }}" + PACKAGE_USER: "${{ secrets.PACKAGE_USER }}" + PACKAGE_TOKEN: "${{ secrets.PACKAGE_TOKEN }}" + PACKAGER: "Joachim Schlöffel " + steps: + - name: Prepare Environment + run: | + apk add --no-cache --update abuild-rootbld alpine-sdk atools-apkbuild-lint bash ca-certificates curl doas git nodejs sudo tar + + - name: Checkout + uses: actions/checkout@v3 + + - name: Build packages + run: | + scripts/apk/clean.sh + apkbuild-lint packaging/alpine/local/seaweedfs/APKBUILD + scripts/apk/ci-build.sh + scripts/apk/list-packages.sh + + - name: Smoke test + run: scripts/apk/ci-smoke.sh + + - name: Publish Alpine packages + if: github.event_name != 'pull_request' + run: scripts/apk/publish-gitea.sh diff --git a/README.md b/README.md index 81f6786..cc719d4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SeaweedFS Alpine Package -Local Alpine packaging for SeaweedFS `4.31`. The workflow runs Alpine tooling +Local Alpine 3.23 packaging for SeaweedFS `4.31`. The workflow runs Alpine tooling inside Docker and writes signed packages under `packages/local//`. ## Commands @@ -17,6 +17,8 @@ mise run apk:packages mise run apk:smoke mise run apk:test-shell mise run apk:publish-check +mise run apk:publish-gitea +mise run gitea-workflow-build mise run apk:shell mise run apk:clean ``` @@ -50,6 +52,12 @@ The package repackages upstream release tarballs, so builds use `ALPINE_BUILD_PLATFORM=linux/amd64` by default. Signing keys and distfiles are cached in `.cache/abuild/` and `.cache/apk-distfiles/`. +The Gitea workflow publishes to the Alpine package registry branch `v3.23`. +Override `ALPINE_VERSION` and `ALPINE_REGISTRY_BRANCH` if you intentionally +build for another Alpine branch, including `edge`. +Use `mise run gitea-workflow-build` for a local `pull_request` workflow check +through `act`; the publish step is skipped for that event. + ## Test And Publish ```sh @@ -116,6 +124,32 @@ rc-service seaweedfs.master start If the repo key is missing on a target system, copy `.cache/abuild/*.rsa.pub` into `/etc/apk/keys/` before `apk update`. +## Gitea Registry + +The Gitea workflow reads these repository variables and secrets: + +```text +Variables: INSTANCE_URL, PACKAGE_OWNER, PACKAGE_NAME +Secrets: PACKAGE_USER, PACKAGE_TOKEN +``` + +It uploads each built `*.apk` with HTTP `PUT` to: + +```text +${INSTANCE_URL}/api/packages/${PACKAGE_OWNER}/alpine/v3.23/${PACKAGE_NAME} +``` + +For local publishing, run: + +```sh +INSTANCE_URL=https://code.factoring.digital \ +PACKAGE_OWNER=fspdigital \ +PACKAGE_NAME=seaweedfs-alpine \ +PACKAGE_USER=... \ +PACKAGE_TOKEN=... \ +mise run apk:publish-gitea +``` + ## Alpine Package Dos And Don'ts Do: diff --git a/mise.toml b/mise.toml index 66ed676..4973c12 100644 --- a/mise.toml +++ b/mise.toml @@ -1,4 +1,5 @@ [tools] +act = "latest" codex = "latest" docker-cli = "latest" shellcheck = "latest" @@ -11,6 +12,10 @@ run = "scripts/apk/build.sh build" description = "Build the Alpine package in Docker for all configured architectures" run = "scripts/apk/build.sh build-all" +[tasks."apk:ci-build"] +description = "Build all configured Alpine packages directly in the current Alpine environment" +run = "scripts/apk/ci-build.sh" + [tasks."apk:build-x86_64"] description = "Build the Alpine package in Docker for x86_64" run = "scripts/apk/build.sh build x86_64" @@ -35,6 +40,10 @@ run = "scripts/apk/build.sh lint" description = "Install-test built packages from the local repository in Docker" run = "scripts/apk/smoke.sh" +[tasks."apk:ci-smoke"] +description = "Install-test built packages directly in the current Alpine environment" +run = "scripts/apk/ci-smoke.sh" + [tasks."apk:test-shell"] description = "Open an Alpine shell with the current local package build installed" run = "scripts/apk/test-shell.sh" @@ -47,6 +56,14 @@ run = "scripts/apk/list-packages.sh" description = "Run lint, multi-arch build, package listing, and smoke test" run = "scripts/apk/publish-check.sh" +[tasks."apk:publish-gitea"] +description = "Publish built packages to the Gitea Alpine package registry" +run = "scripts/apk/publish-gitea.sh" + +[tasks."gitea-workflow-build"] +description = "Run the Gitea build workflow locally through act as a pull_request event" +run = "act pull_request -W .gitea/workflows/build.yml -j build-and-publish" + [tasks."apk:shell"] description = "Open an Alpine package build shell in Docker" run = "scripts/apk/build.sh shell" diff --git a/packaging/alpine/local/seaweedfs/APKBUILD b/packaging/alpine/local/seaweedfs/APKBUILD index 8237ea2..c2b91fb 100644 --- a/packaging/alpine/local/seaweedfs/APKBUILD +++ b/packaging/alpine/local/seaweedfs/APKBUILD @@ -1,13 +1,13 @@ -# Maintainer: Local Builder +# Maintainer: Joachim Schlöffel pkgname=seaweedfs pkgver=4.31 -pkgrel=2 +pkgrel=3 pkgdesc="Distributed storage system for object storage, file systems, and Iceberg tables" url="https://github.com/seaweedfs/seaweedfs" arch="x86_64 aarch64" license="Apache-2.0" -depends="ca-certificates" -provides="!$pkgname-openrc" +depends="ca-certificates /bin/sh" +provides="!$pkgname-openrc cmd:weed=$pkgver-r$pkgrel" install="$pkgname.pre-install" subpackages=" $pkgname-admin-openrc:_openrc_admin:noarch @@ -21,7 +21,7 @@ subpackages=" $pkgname-webdav-openrc:_openrc_webdav:noarch $pkgname-worker-openrc:_openrc_worker:noarch " -options="!check !strip" +options="!check !strip !tracedeps" case "$CARCH" in x86_64) diff --git a/scripts/apk/Dockerfile b/scripts/apk/Dockerfile index 27da1c5..b36772b 100644 --- a/scripts/apk/Dockerfile +++ b/scripts/apk/Dockerfile @@ -1,4 +1,4 @@ -ARG ALPINE_VERSION=3.22 +ARG ALPINE_VERSION=3.23 FROM alpine:${ALPINE_VERSION} ARG BUILDER_UID=1000 diff --git a/scripts/apk/build.sh b/scripts/apk/build.sh index 3f3f6b8..fab66e4 100755 --- a/scripts/apk/build.sh +++ b/scripts/apk/build.sh @@ -4,9 +4,18 @@ set -euo pipefail command_name="${1:-build}" requested_arch="${2:-${ALPINE_ARCH:-x86_64}}" repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -alpine_version="${ALPINE_VERSION:-3.22}" +alpine_version="${ALPINE_VERSION:-3.23}" repo_name="${ALPINE_REPO_NAME:-local}" build_platform="${ALPINE_BUILD_PLATFORM:-linux/amd64}" +builder_uid="$(id -u)" +builder_gid="$(id -g)" + +if [[ "${builder_uid}" == "0" ]]; then + builder_uid=1000 +fi +if [[ "${builder_gid}" == "0" ]]; then + builder_gid=1000 +fi validate_arch() { case "$1" in @@ -26,8 +35,8 @@ run_for_arch() { docker build \ --platform "${build_platform}" \ --build-arg "ALPINE_VERSION=${alpine_version}" \ - --build-arg "BUILDER_UID=$(id -u)" \ - --build-arg "BUILDER_GID=$(id -g)" \ + --build-arg "BUILDER_UID=${builder_uid}" \ + --build-arg "BUILDER_GID=${builder_gid}" \ -f "${repo_root}/scripts/apk/Dockerfile" \ -t "${image_name}" \ "${repo_root}" @@ -41,7 +50,7 @@ run_for_arch() { -e "ALPINE_ARCH=${arch}" \ -e "CARCH=${arch}" \ -e "ALPINE_REPO_NAME=${repo_name}" \ - -e "PACKAGER=${PACKAGER:-Local Builder }" \ + -e "PACKAGER=${PACKAGER:-Joachim Schlöffel }" \ -v "${repo_root}:/work" \ -v "${repo_root}/.cache/abuild:/home/builder/.abuild" \ -v "${repo_root}/.cache/apk-distfiles:/var/cache/distfiles" \ diff --git a/scripts/apk/ci-build.sh b/scripts/apk/ci-build.sh new file mode 100755 index 0000000..3e57e3e --- /dev/null +++ b/scripts/apk/ci-build.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +package_dir="${APKBUILD_DIR:-${repo_root}/packaging/alpine/local/seaweedfs}" +repo_name="${ALPINE_REPO_NAME:-local}" +arches="${ALPINE_ARCHES:-x86_64 aarch64}" +packager="${PACKAGER:-Joachim Schlöffel }" + +if [[ "${1:-}" != "--as-builder" && "$(id -u)" == "0" ]]; then + addgroup -g 1000 builder 2>/dev/null || addgroup builder + adduser -D -u 1000 -G builder builder 2>/dev/null || true + addgroup builder abuild + addgroup builder wheel + printf 'permit nopass :wheel\n' > /etc/doas.d/wheel.conf + printf '%%wheel ALL=(ALL) NOPASSWD: ALL\n' > /etc/sudoers.d/wheel + chown -R builder:builder "${repo_root}" + exec su builder -c "ALPINE_ARCHES='${arches}' ALPINE_REPO_NAME='${repo_name}' PACKAGER='${packager}' '${BASH_SOURCE[0]}' --as-builder" +fi + +export PACKAGER="${packager}" +export REPODEST="${repo_root}/packages" +export SRCDEST="${repo_root}/.cache/apk-distfiles" + +git config --global --add safe.directory "${repo_root}" +mkdir -p "${repo_root}/.cache/abuild" "${SRCDEST}" "${REPODEST}" "${HOME}/.abuild" + +if [[ ! -e "${HOME}/.abuild/abuild.conf" && -d "${repo_root}/.cache/abuild" ]]; then + rmdir "${HOME}/.abuild" 2>/dev/null || true + ln -s "${repo_root}/.cache/abuild" "${HOME}/.abuild" +fi + +if ! compgen -G "${HOME}/.abuild/*.rsa" > /dev/null; then + abuild-keygen -a -n +fi + +doas cp "${HOME}"/.abuild/*.rsa.pub /etc/apk/keys/ + +for arch in ${arches}; do + case "${arch}" in + x86_64|aarch64) ;; + *) printf 'unsupported Alpine architecture: %s\n' "${arch}" >&2; exit 2 ;; + esac + + printf 'Building %s for %s\n' "$(basename "${package_dir}")" "${arch}" + ( + export ALPINE_ARCH="${arch}" + export CARCH="${arch}" + cd "${package_dir}" + abuild -r + ) + + mkdir -p "${REPODEST}/${repo_name}/${arch}" + cp "${HOME}"/.abuild/*.rsa.pub "${REPODEST}/${repo_name}/${arch}/" +done diff --git a/scripts/apk/ci-smoke.sh b/scripts/apk/ci-smoke.sh new file mode 100755 index 0000000..7905faa --- /dev/null +++ b/scripts/apk/ci-smoke.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +repo_name="${ALPINE_REPO_NAME:-local}" +arch="${ALPINE_ARCH:-$(apk --print-arch)}" +packages="${SMOKE_PACKAGES:-seaweedfs seaweedfs-master-openrc seaweedfs-filer-openrc seaweedfs-worker-openrc}" +repo_dir="${repo_root}/packages/${repo_name}/${arch}" +read -r -a package_list <<< "${packages}" + +if [[ ! -d "${repo_dir}" ]]; then + printf 'missing local repository: packages/%s/%s\n' "${repo_name}" "${arch}" >&2 + exit 1 +fi + +cp "${repo_dir}"/*.rsa.pub /etc/apk/keys/ +echo "${repo_root}/packages/${repo_name}" >> /etc/apk/repositories +apk update +apk add "${package_list[@]}" +weed version +ls -1 /etc/seaweedfs +if ls /etc/init.d/seaweedfs.* >/dev/null 2>&1; then + find /etc/init.d -maxdepth 1 -name 'seaweedfs.*' -print | sort +fi diff --git a/scripts/apk/container-entrypoint.sh b/scripts/apk/container-entrypoint.sh index fd23434..50d2529 100755 --- a/scripts/apk/container-entrypoint.sh +++ b/scripts/apk/container-entrypoint.sh @@ -7,7 +7,7 @@ repo_name="${ALPINE_REPO_NAME:-local}" arch="${ALPINE_ARCH:-x86_64}" export CARCH="${CARCH:-$arch}" -export PACKAGER="${PACKAGER:-Local Builder }" +export PACKAGER="${PACKAGER:-Joachim Schlöffel }" if [[ -n "${PACKAGER_PRIVKEY:-}" ]]; then export PACKAGER_PRIVKEY fi diff --git a/scripts/apk/publish-gitea.sh b/scripts/apk/publish-gitea.sh new file mode 100755 index 0000000..92fbefd --- /dev/null +++ b/scripts/apk/publish-gitea.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +repo_name="${ALPINE_REPO_NAME:-local}" +alpine_version="${ALPINE_VERSION:-3.23}" +branch="${ALPINE_REGISTRY_BRANCH:-v${alpine_version}}" +repository="${ALPINE_REGISTRY_REPOSITORY:-${PACKAGE_NAME:-main}}" +instance_url="${INSTANCE_URL:-}" +owner="${PACKAGE_OWNER:-}" +user="${PACKAGE_USER:-}" +token="${PACKAGE_TOKEN:-}" +allow_conflicts="${GITEA_APK_ALLOW_CONFLICTS:-1}" + +require_env() { + local name="$1" + local value="$2" + + if [[ -z "${value}" ]]; then + printf 'missing required environment variable: %s\n' "${name}" >&2 + exit 2 + fi +} + +require_env INSTANCE_URL "${instance_url}" +require_env PACKAGE_OWNER "${owner}" +require_env PACKAGE_USER "${user}" +require_env PACKAGE_TOKEN "${token}" + +instance_url="${instance_url%/}" +upload_url="${instance_url}/api/packages/${owner}/alpine/${branch}/${repository}" +package_root="${repo_root}/packages/${repo_name}" + +if [[ ! -d "${package_root}" ]]; then + printf 'missing local package repository: %s\n' "${package_root}" >&2 + printf 'run: mise run apk:build-all\n' >&2 + exit 1 +fi + +shopt -s nullglob +apks=("${package_root}"/*/*.apk) +shopt -u nullglob + +if [[ "${#apks[@]}" -eq 0 ]]; then + printf 'no APK files found under %s\n' "${package_root}" >&2 + exit 1 +fi + +printf 'Publishing %d APK files to %s\n' "${#apks[@]}" "${upload_url}" + +for apk in "${apks[@]}"; do + filename="$(basename "${apk}")" + status="$( + curl --silent --show-error --location \ + --user "${user}:${token}" \ + --upload-file "${apk}" \ + --write-out '%{http_code}' \ + --output /tmp/gitea-apk-publish-response \ + "${upload_url}" + )" + + case "${status}" in + 201) + printf 'published: %s\n' "${filename}" + ;; + 409) + if [[ "${allow_conflicts}" == "1" ]]; then + printf 'already exists: %s\n' "${filename}" + else + printf 'conflict: %s already exists\n' "${filename}" >&2 + cat /tmp/gitea-apk-publish-response >&2 + exit 1 + fi + ;; + *) + printf 'publish failed for %s: HTTP %s\n' "${filename}" "${status}" >&2 + cat /tmp/gitea-apk-publish-response >&2 + exit 1 + ;; + esac +done diff --git a/scripts/apk/smoke.sh b/scripts/apk/smoke.sh index a8c31f3..d5c5b4b 100755 --- a/scripts/apk/smoke.sh +++ b/scripts/apk/smoke.sh @@ -2,7 +2,7 @@ set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -alpine_version="${ALPINE_VERSION:-3.22}" +alpine_version="${ALPINE_VERSION:-3.23}" platform="${ALPINE_BUILD_PLATFORM:-linux/amd64}" arch="${ALPINE_ARCH:-x86_64}" packages="${SMOKE_PACKAGES:-seaweedfs seaweedfs-master-openrc seaweedfs-filer-openrc seaweedfs-worker-openrc}" diff --git a/scripts/apk/test-shell.sh b/scripts/apk/test-shell.sh index 04f95fd..d0e0e76 100755 --- a/scripts/apk/test-shell.sh +++ b/scripts/apk/test-shell.sh @@ -2,7 +2,7 @@ set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -alpine_version="${ALPINE_VERSION:-3.22}" +alpine_version="${ALPINE_VERSION:-3.23}" platform="${ALPINE_BUILD_PLATFORM:-linux/amd64}" arch="${ALPINE_ARCH:-x86_64}" packages="${TEST_SHELL_PACKAGES:-seaweedfs seaweedfs-doc bash-completion seaweedfs-master-openrc seaweedfs-filer-openrc seaweedfs-worker-openrc}"