From 29c79708f1baf67bd8e4363e47efcd573dc7a78b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joachim=20Schl=C3=B6ffel?= <3+fspjo@noreply.localhost> Date: Wed, 7 Oct 2026 14:24:24 +0200 Subject: [PATCH] add rclone-openrc (#21) Reviewed-on: https://code.factoring.digital/public/alpine-packages/pulls/21 --- .gitea/workflows/build.yml | 48 +++++---------- README.md | 4 ++ packaging/alpine/local/rclone-openrc/APKBUILD | 29 +++++++++ .../alpine/local/rclone-openrc/README.md | 59 +++++++++++++++++++ .../rclone-openrc/rclone-openrc.pre-install | 7 +++ .../alpine/local/rclone-openrc/rclone.confd | 23 ++++++++ .../alpine/local/rclone-openrc/rclone.initd | 31 ++++++++++ .../rclone-openrc/scripts/runtime-test.sh | 20 +++++++ .../rclone-openrc/scripts/test-install.sh | 36 +++++++++++ scripts/apk/ci-smoke.sh | 7 ++- scripts/apk/smoke.sh | 7 ++- scripts/apk/test-shell.sh | 1 + 12 files changed, 237 insertions(+), 35 deletions(-) create mode 100644 packaging/alpine/local/rclone-openrc/APKBUILD create mode 100644 packaging/alpine/local/rclone-openrc/README.md create mode 100755 packaging/alpine/local/rclone-openrc/rclone-openrc.pre-install create mode 100644 packaging/alpine/local/rclone-openrc/rclone.confd create mode 100644 packaging/alpine/local/rclone-openrc/rclone.initd create mode 100755 packaging/alpine/local/rclone-openrc/scripts/runtime-test.sh create mode 100755 packaging/alpine/local/rclone-openrc/scripts/test-install.sh diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 492c441..7010367 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -62,6 +62,18 @@ jobs: id: package-selection run: scripts/apk/ci-package-cache.sh prepare + - name: Validate selected package matrix + run: | + for package in ${ALPINE_PACKAGES}; do + case "${package}" in + seaweedfs|greptimedb|lavinmq|gitea|nifi|rclone-openrc) ;; + *) + printf 'selected package is missing from the CI build matrix: %s\n' "${package}" >&2 + exit 1 + ;; + esac + done + build: needs: preflight if: needs.preflight.outputs.packages_selected == 'true' @@ -70,7 +82,7 @@ jobs: strategy: fail-fast: false matrix: - package: [seaweedfs, greptimedb, lavinmq, gitea, nifi] + package: [seaweedfs, greptimedb, lavinmq, gitea, nifi, rclone-openrc] container: image: "alpine:3.24" env: @@ -136,40 +148,10 @@ jobs: scripts/apk/ci-package-cache.sh prepare printf 'ALPINE_PACKAGES=%s\n' "$selected_packages" >> "$GITHUB_ENV" - - name: Download SeaweedFS artifacts - if: contains(needs.preflight.outputs.selected_packages, 'seaweedfs') + - name: Download package artifacts uses: actions/download-artifact@v3 with: - name: alpine-package-seaweedfs-${{ github.sha }} - path: packages/worker-artifacts/seaweedfs - - - name: Download GreptimeDB artifacts - if: contains(needs.preflight.outputs.selected_packages, 'greptimedb') - uses: actions/download-artifact@v3 - with: - name: alpine-package-greptimedb-${{ github.sha }} - path: packages/worker-artifacts/greptimedb - - - name: Download LavinMQ artifacts - if: contains(needs.preflight.outputs.selected_packages, 'lavinmq') - uses: actions/download-artifact@v3 - with: - name: alpine-package-lavinmq-${{ github.sha }} - path: packages/worker-artifacts/lavinmq - - - name: Download Gitea artifacts - if: contains(needs.preflight.outputs.selected_packages, 'gitea') - uses: actions/download-artifact@v3 - with: - name: alpine-package-gitea-${{ github.sha }} - path: packages/worker-artifacts/gitea - - - name: Download NiFi artifacts - if: contains(needs.preflight.outputs.selected_packages, 'nifi') - uses: actions/download-artifact@v3 - with: - name: alpine-package-nifi-${{ github.sha }} - path: packages/worker-artifacts/nifi + path: packages/worker-artifacts - name: Merge package artifacts run: scripts/apk/merge-ci-artifacts.sh diff --git a/README.md b/README.md index b2c2daa..5b18ab5 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ https://code.factoring.digital/api/packages/public/alpine/v3.24/alpine-packages | [LavinMQ](packaging/alpine/local/lavinmq/README.md) | `lavinmq` | `2.10.1` | Source build from upstream release | Provides a current LavinMQ build on Alpine 3.24 with matching Crystal support. | | [Gitea](packaging/alpine/local/gitea/README.md) | `gitea` | `28.1.0` | Binary repackaging of official upstream release binaries | Ships current Gitea releases with the expected Alpine runtime layout and OpenRC service. | | [Apache NiFi](packaging/alpine/local/nifi/README.md) | `nifi` | `2.12.0` | Binary repackaging of the official Standard distribution | Provides Apache NiFi 2 with a secure default configuration and OpenRC service. | +| [rclone OpenRC](packaging/alpine/local/rclone-openrc/README.md) | `rclone-openrc` | `0.1.0` | OpenRC integration for Alpine's rclone package | Provides reusable, instance-capable OpenRC supervision for rclone commands. | ## Use In Production @@ -168,6 +169,9 @@ weed version apk add greptimedb greptimedb-docs greptimedb-standalone-openrc greptime --version + +apk add rclone-openrc +rclone version ``` Run package-local runtime tests: diff --git a/packaging/alpine/local/rclone-openrc/APKBUILD b/packaging/alpine/local/rclone-openrc/APKBUILD new file mode 100644 index 0000000..4a03c8a --- /dev/null +++ b/packaging/alpine/local/rclone-openrc/APKBUILD @@ -0,0 +1,29 @@ +# Maintainer: Joachim Schlöffel +pkgname=rclone-openrc +pkgver=0.1.0 +pkgrel=0 +pkgdesc="Generic, instance-capable OpenRC service for rclone" +url="https://rclone.org/" +arch="noarch" +license="MIT" +depends="openrc rclone" +install="$pkgname.pre-install" +pkgusers="rclone" +pkggroups="rclone" +options="!check" +source=" + rclone.confd + rclone.initd + " + +package() { + install -d -m 0750 -o root -g rclone "$pkgdir"/etc/rclone + install -d -m 0750 -o rclone -g rclone "$pkgdir"/var/lib/rclone/cache + install -Dm644 "$srcdir"/rclone.confd "$pkgdir"/etc/conf.d/rclone + install -Dm755 "$srcdir"/rclone.initd "$pkgdir"/etc/init.d/rclone +} + +sha512sums=" +91a05da2c5740a810faf0226ab5fcb3d92a9db56b42c10decaed91481d3129cfc37f5f726a6dc1b5b1246ad4d1386cbe918e109f152969912ea670dbff35fe70 rclone.confd +e92ce86aa21ce1a94beb8304c806b4c0045d412477d7a232e37b0f28354af3060de054c5ae3d1ea353b92d19137a0cfa00b0891b0e179aa32bc6b2c7ecdfc7ea rclone.initd +" diff --git a/packaging/alpine/local/rclone-openrc/README.md b/packaging/alpine/local/rclone-openrc/README.md new file mode 100644 index 0000000..00d0fb0 --- /dev/null +++ b/packaging/alpine/local/rclone-openrc/README.md @@ -0,0 +1,59 @@ +# rclone OpenRC Alpine Package + +Provides a generic, instance-capable OpenRC service for Alpine's `rclone` +package. It does not repackage the rclone binary. + +## Install + +```sh +apk add rclone-openrc +``` + +The package creates the unprivileged `rclone` account, `/etc/rclone` for +configuration and credentials, and `/var/lib/rclone/cache` for rclone state. +It deliberately does not create remotes, credentials, authorized-key files, or +SSH host keys; provision those as machine-specific secrets. + +## Create an Instance + +Copy the template and symlink the generic service under an instance name: + +```sh +cp /etc/conf.d/rclone /etc/conf.d/rclone.sftp +ln -s rclone /etc/init.d/rclone.sftp +``` + +For an SFTP server, configure `/etc/conf.d/rclone.sftp`: + +```sh +RCLONE_COMMAND="serve sftp" +RCLONE_REMOTE="/srv/storage" +RCLONE_ARGS=" + --addr :2022 + --authorized-keys /etc/rclone/authorized_keys + --key /etc/rclone/ssh_host_ed25519_key + --log-level INFO +" +``` + +Then enable and start it: + +```sh +rc-update add rclone.sftp default +rc-service rclone.sftp start +``` + +Other instances, such as `rclone.webdav` or `rclone.rcd`, use their own +`/etc/conf.d/rclone.` file with the same generic init script. The service +runs rclone in the foreground under `supervise-daemon`, which restarts it after +an unexpected exit. Set `RCLONE_RESPAWN_MAX` to a positive value to limit +restart attempts. + +## Package Commands + +```sh +ALPINE_PACKAGE=rclone-openrc mise run apk:checksum +ALPINE_PACKAGE=rclone-openrc mise run apk:build +ALPINE_PACKAGE=rclone-openrc SKIP_BUILD=1 mise run apk:test-install +ALPINE_PACKAGE=rclone-openrc mise run apk:runtime-test +``` diff --git a/packaging/alpine/local/rclone-openrc/rclone-openrc.pre-install b/packaging/alpine/local/rclone-openrc/rclone-openrc.pre-install new file mode 100755 index 0000000..f966aca --- /dev/null +++ b/packaging/alpine/local/rclone-openrc/rclone-openrc.pre-install @@ -0,0 +1,7 @@ +#!/bin/sh + +addgroup -S rclone 2>/dev/null +adduser -S -D -H -h /var/lib/rclone -s /sbin/nologin \ + -G rclone -g rclone rclone 2>/dev/null + +exit 0 diff --git a/packaging/alpine/local/rclone-openrc/rclone.confd b/packaging/alpine/local/rclone-openrc/rclone.confd new file mode 100644 index 0000000..7c94440 --- /dev/null +++ b/packaging/alpine/local/rclone-openrc/rclone.confd @@ -0,0 +1,23 @@ +# Defaults for rclone OpenRC instances. +# +# To create an instance, copy this file to /etc/conf.d/rclone. and create +# /etc/init.d/rclone. as a symlink to /etc/init.d/rclone. For example: +# +# cp /etc/conf.d/rclone /etc/conf.d/rclone.sftp +# ln -s rclone /etc/init.d/rclone.sftp +# +# Each instance must set RCLONE_COMMAND. RCLONE_REMOTE is optional because +# commands such as "rcd" do not use a remote. +RCLONE_COMMAND="" +RCLONE_REMOTE="" +RCLONE_ARGS="" + +RCLONE_CONFIG="/etc/rclone/rclone.conf" +RCLONE_CACHE_DIR="/var/lib/rclone/cache" +RCLONE_USER="rclone" +RCLONE_GROUP="rclone" + +# Restart a long-running rclone command after an unexpected exit. Set +# RCLONE_RESPAWN_MAX to a positive number to limit restart attempts. +RCLONE_RESPAWN_DELAY="5" +RCLONE_RESPAWN_MAX="0" diff --git a/packaging/alpine/local/rclone-openrc/rclone.initd b/packaging/alpine/local/rclone-openrc/rclone.initd new file mode 100644 index 0000000..451d73b --- /dev/null +++ b/packaging/alpine/local/rclone-openrc/rclone.initd @@ -0,0 +1,31 @@ +#!/sbin/openrc-run + +instance_name="${RC_SVCNAME#rclone.}" +description="rclone ${instance_name} service" + +supervisor="supervise-daemon" +command="/usr/bin/rclone" +command_user="${RCLONE_USER:-rclone}:${RCLONE_GROUP:-rclone}" +pidfile="/run/rclone/${RC_SVCNAME}.pid" +respawn_delay="${RCLONE_RESPAWN_DELAY:-5}" +respawn_max="${RCLONE_RESPAWN_MAX:-0}" +supervise_daemon_args="--chdir /var/lib/rclone --pidfile ${pidfile}" +command_args="${RCLONE_COMMAND:-} ${RCLONE_REMOTE:-} --config ${RCLONE_CONFIG:-/etc/rclone/rclone.conf} --cache-dir ${RCLONE_CACHE_DIR:-/var/lib/rclone/cache} ${RCLONE_ARGS:-}" + +depend() { + need net + use dns logger +} + +start_pre() { + local service_owner="${RCLONE_USER:-rclone}:${RCLONE_GROUP:-rclone}" + + if [ -z "${RCLONE_COMMAND:-}" ]; then + eerror "RCLONE_COMMAND is required; configure /etc/conf.d/${RC_SVCNAME}" + return 1 + fi + + checkpath -d -m 0750 -o "$service_owner" \ + /run/rclone \ + "${RCLONE_CACHE_DIR:-/var/lib/rclone/cache}" +} diff --git a/packaging/alpine/local/rclone-openrc/scripts/runtime-test.sh b/packaging/alpine/local/rclone-openrc/scripts/runtime-test.sh new file mode 100755 index 0000000..2180b6a --- /dev/null +++ b/packaging/alpine/local/rclone-openrc/scripts/runtime-test.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +apk add rclone-openrc + +test -x /usr/bin/rclone +test -f /etc/conf.d/rclone +test -f /etc/init.d/rclone +test "$(stat -c '%U:%G %a' /etc/rclone)" = "root:rclone 750" +test "$(stat -c '%U:%G %a' /var/lib/rclone/cache)" = "rclone:rclone 750" + +cp /etc/conf.d/rclone /etc/conf.d/rclone.rcd +ln -s rclone /etc/init.d/rclone.rcd +sed -i 's|^RCLONE_COMMAND=""|RCLONE_COMMAND="rcd"|' /etc/conf.d/rclone.rcd +sed -i 's|^RCLONE_ARGS=""|RCLONE_ARGS="--rc-addr 127.0.0.1:5572 --rc-no-auth"|' /etc/conf.d/rclone.rcd + +rc-update add rclone.rcd default || true +rc-service rclone.rcd start +rc-service rclone.rcd status +rc-service rclone.rcd stop diff --git a/packaging/alpine/local/rclone-openrc/scripts/test-install.sh b/packaging/alpine/local/rclone-openrc/scripts/test-install.sh new file mode 100755 index 0000000..9be3fae --- /dev/null +++ b/packaging/alpine/local/rclone-openrc/scripts/test-install.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="${REPO_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../../.." && pwd)}" +# shellcheck source=scripts/apk/test-install-lib.sh +. "${repo_root}/scripts/apk/test-install-lib.sh" + +apk_test_install_run "${repo_root}" "${PACKAGE_NAME:-rclone-openrc}" "${PACKAGE_VERSION:-}" <<'SCRIPT' + set -e + + if [ -n "${ALPINE_REPOSITORY_URL}" ]; then + if [ -n "${ALPINE_REPOSITORY_KEY_URL}" ]; then + apk add --no-cache curl >/dev/null + cd /etc/apk/keys + curl -fsSLOJ "${ALPINE_REPOSITORY_KEY_URL}" + fi + printf "%s\n" "${ALPINE_REPOSITORY_URL}" >> /etc/apk/repositories + else + cp "/repo/${ALPINE_ARCH}"/*.rsa.pub /etc/apk/keys/ + printf "%s\n" /repo >> /etc/apk/repositories + fi + apk update + apk add "${PACKAGE_NAME}" + + test -x /usr/bin/rclone + test -f /etc/conf.d/rclone + test -f /etc/init.d/rclone + test -d /etc/rclone + test "$(stat -c '%U:%G %a' /etc/rclone)" = "root:rclone 750" + test "$(stat -c '%U:%G %a' /var/lib/rclone/cache)" = "rclone:rclone 750" + + cp /etc/conf.d/rclone /etc/conf.d/rclone.sftp + ln -s rclone /etc/init.d/rclone.sftp + rc-update add rclone.sftp default + test -L /etc/runlevels/default/rclone.sftp +SCRIPT diff --git a/scripts/apk/ci-smoke.sh b/scripts/apk/ci-smoke.sh index 4aa4f29..c6b6e5d 100755 --- a/scripts/apk/ci-smoke.sh +++ b/scripts/apk/ci-smoke.sh @@ -6,7 +6,7 @@ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" . "${repo_root}/scripts/apk/package-lib.sh" 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 greptimedb lavinmq lavinmq-openrc gitea gitea-openrc}" +packages="${SMOKE_PACKAGES:-seaweedfs seaweedfs-master-openrc seaweedfs-filer-openrc seaweedfs-worker-openrc greptimedb lavinmq lavinmq-openrc gitea gitea-openrc rclone-openrc}" repo_dir="${repo_root}/packages/${repo_name}/${arch}" registry_url="${ALPINE_REGISTRY_URL:-}" registry_key="${ALPINE_REGISTRY_KEY:-}" @@ -64,6 +64,11 @@ if command -v gitea >/dev/null 2>&1; then test -f /etc/gitea/app.ini test -d /var/lib/gitea fi +if command -v rclone >/dev/null 2>&1; then + rclone version + test -f /etc/conf.d/rclone + test -f /etc/init.d/rclone +fi 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/smoke.sh b/scripts/apk/smoke.sh index 058ae81..529e1b4 100755 --- a/scripts/apk/smoke.sh +++ b/scripts/apk/smoke.sh @@ -5,7 +5,7 @@ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" alpine_version="${ALPINE_VERSION:-3.24}" platform="${ALPINE_BUILD_PLATFORM:-linux/amd64}" arch="${ALPINE_ARCH:-x86_64}" -packages="${SMOKE_PACKAGES:-seaweedfs seaweedfs-master-openrc seaweedfs-filer-openrc seaweedfs-worker-openrc greptimedb lavinmq lavinmq-openrc gitea gitea-openrc}" +packages="${SMOKE_PACKAGES:-seaweedfs seaweedfs-master-openrc seaweedfs-filer-openrc seaweedfs-worker-openrc greptimedb lavinmq lavinmq-openrc gitea gitea-openrc rclone-openrc}" if [[ ! -d "${repo_root}/packages/local/${arch}" ]]; then printf 'missing local repository: packages/local/%s\n' "${arch}" >&2 @@ -39,6 +39,11 @@ docker run --rm --platform "${platform}" \ test -f /etc/gitea/app.ini test -d /var/lib/gitea fi + if command -v rclone >/dev/null 2>&1; then + rclone version + test -f /etc/conf.d/rclone + test -f /etc/init.d/rclone + fi if ls /etc/init.d/seaweedfs.* >/dev/null 2>&1; then ls -1 /etc/init.d/seaweedfs.* | sort fi diff --git a/scripts/apk/test-shell.sh b/scripts/apk/test-shell.sh index 4bb9af3..0682719 100755 --- a/scripts/apk/test-shell.sh +++ b/scripts/apk/test-shell.sh @@ -76,6 +76,7 @@ Examples: apk add greptimedb greptimedb-docs greptimedb-standalone-openrc apk add lavinmq lavinmq-openrc apk add gitea gitea-openrc + apk add rclone-openrc Entering ${TEST_SHELL}. -- 2.54.0