Files
MoShiro/ci/publish-openwrt.sh
justme a6f9759238
ci/woodpecker/push/web-build Pipeline was successful
ci/woodpecker/push/verify Pipeline was successful
ci/woodpecker/push/dev-image Pipeline failed
ci: build musl only for arm64 and reuse toolchain cache [CI]
2026-10-21 12:41:58 +08:00

69 lines
4.0 KiB
Bash

#!/usr/bin/env bash
# Package an already-built native Agent and publish an independently signed feed.
# Used after verify/web-build gates for dev, and after all release builds for tags.
set -euo pipefail
channel="${1:?usage: publish-openwrt.sh <dev|release> <rid> <binary-directory>}"
rid="${2:?}"; source_dir="${3:?}"
[[ "$channel" == dev || "$channel" == release ]] || exit 2
[[ "$rid" == linux-musl-arm64 ]] || exit 2
if [[ "$channel" == release ]]; then
[[ "${CI_COMMIT_TAG:-}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo 'release feed requires a stable version tag' >&2; exit 2; }
else
[[ -z "${CI_COMMIT_TAG:-}" && "${CI_PIPELINE_EVENT:-}" == push && "${CI_COMMIT_BRANCH:-}" == main && "${CI_COMMIT_MESSAGE:-}" == *'[CI]'* ]] \
|| { echo 'dev feed requires a main push with [CI]' >&2; exit 2; }
fi
[[ -n "${RELEASE_SIGNING_KEY:-}" && -n "${GITEA_TOKEN:-}" ]] || { echo 'OpenWrt publishing requires signing and Gitea secrets' >&2; exit 2; }
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
source "$root/ci/version.sh"
version="$MOSHIRO_VERSION"
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
mkdir "$work/stage"
install -m 755 "$source_dir/moshiro-agent" "$source_dir/fleet-enroll" "$work/stage/"
for library in "$source_dir"/lib*.so.*; do
[[ ! -f "$library" ]] || install -m 644 "$library" "$work/stage/"
done
install -m 755 "$root/packaging/openwrt/install.sh" "$work/stage/openwrt.sh"
install -m 755 "$root/packaging/openwrt/native-agent.init" "$work/stage/agent.init"
install -m 755 "$root/packaging/openwrt/updater.init" "$work/stage/updater.init"
members=()
for file in "$work/stage/"*; do members+=("$(basename "$file")"); done
tar --sort=name --owner=0 --group=0 --numeric-owner -czf "$work/package.tar.gz" -C "$work/stage" "${members[@]}"
hash="$(sha256sum "$work/package.tar.gz" | cut -d' ' -f1)"
size="$(stat -c%s "$work/package.tar.gz")"
package="agent-$rid-$hash.tar.gz"
seq="$(git -C "$root" show -s --format=%ct HEAD)"
# Commit time is stable across retries; equal timestamps intentionally do not auto-upgrade.
python3 - "$channel" "$rid" "$version" "$package" "$hash" "$size" "$seq" > "$work/manifest.json" <<'PY'
import json, sys
channel, rid, version, package, sha256, size, seq = sys.argv[1:]
print(json.dumps(dict(schema=1, component='agent', channel=channel, rid=rid,
version=version, package=package, sha256=sha256, size=int(size), seq=int(seq)), separators=(',', ':')), end='')
PY
chmod 700 "$work"
printf '%s\n' "$RELEASE_SIGNING_KEY" > "$work/key.pem"
chmod 600 "$work/key.pem"
openssl dgst -sha256 -sign "$work/key.pem" -out "$work/signature" "$work/manifest.json"
base64 -w0 "$work/signature" > "$work/manifest.sig"
base="${GITEA_BASE:-http://127.0.0.1:3100}"
base="${base%/}/api/packages/justme/generic/moshiro-openwrt"
# Credentials only in a protected curl config, never command-line arguments or output.
printf 'header = "Authorization: token %s"\n' "$GITEA_TOKEN" > "$work/curl.conf"
chmod 600 "$work/curl.conf"
upload() {
local path="$1" file="$2" mutable="${3:-false}" code
if [[ "$mutable" == true ]]; then
code="$(curl --silent --show-error --config "$work/curl.conf" --request DELETE --output "$work/response" --write-out '%{http_code}' "$base/$path")"
[[ "$code" == 204 || "$code" == 200 || "$code" == 404 ]] || { echo "cannot replace OpenWrt feed: HTTP $code" >&2; return 1; }
fi
code="$(curl --silent --show-error --retry 3 --config "$work/curl.conf" --upload-file "$file" --output "$work/response" --write-out '%{http_code}' "$base/$path")"
# Immutable content-addressed archives may already exist after a retry.
[[ "$code" == 2* || ( "$mutable" == false && "$code" == 409 ) ]] || { echo "OpenWrt upload failed: HTTP $code" >&2; return 1; }
}
upload "$version/$package" "$work/package.tar.gz"
# A transient manifest/signature mismatch is rejected, then retried by the device.
# Publish the channel pointer only after its immutable payload is downloadable.
upload "$channel/manifest-$rid.sig" "$work/manifest.sig" true
upload "$channel/manifest-$rid.json" "$work/manifest.json" true
echo "published signed OpenWrt Agent $version ($channel/$rid)"