mirror of
https://github.com/Mo3he/Axis_Cam_Tailscale.git
synced 2026-09-19 14:24:14 +00:00
Add upstream-tracking release pipeline; extract build.sh
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"app": "Tailscale_VPN",
|
||||
"friendlyName": "Tailscale VPN",
|
||||
"versionPolicy": "mirror",
|
||||
"upstream": {
|
||||
"type": "script",
|
||||
"script": "ci/upstream-version.sh",
|
||||
"name": "Tailscale",
|
||||
"changesUrl": "https://tailscale.com/changelog"
|
||||
},
|
||||
"build": {
|
||||
"command": "./build.sh",
|
||||
"env": {}
|
||||
},
|
||||
"pins": [],
|
||||
"signing": {
|
||||
"skip": [
|
||||
"*_acap3.eap",
|
||||
"*_root.eap"
|
||||
]
|
||||
}
|
||||
}
|
||||
+131
-188
@@ -1,205 +1,148 @@
|
||||
name: Auto Build & Release Tailscale ACAP
|
||||
---
|
||||
# GENERATED by acap-ci.sh from Axis_Cam_Template/ci/build.yml.tmpl
|
||||
# Per-repo settings live in .acap.json. Do not edit this file directly.
|
||||
#
|
||||
# Upstream release -> build -> DRAFT release holding unsigned .eap files.
|
||||
# Signing is manual (Axis has no signing API); ../acap-sign.sh uploads the
|
||||
# signed packages and publishes the release. The acap-ops repo notifies.
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: "0 0 * * *" # Every Monday at 03:00 UTC
|
||||
- cron: "0 3 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version to build. Empty resolves from upstream."
|
||||
required: false
|
||||
force:
|
||||
description: "Rebuild and re-cut the draft even if unchanged."
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: acap-release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build-and-release:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
build: ${{ steps.decide.outputs.build }}
|
||||
release: ${{ steps.decide.outputs.release }}
|
||||
version: ${{ steps.decide.outputs.version }}
|
||||
upstream: ${{ steps.decide.outputs.upstream }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- id: decide
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
INPUT_VERSION: ${{ github.event.inputs.version }}
|
||||
INPUT_FORCE: ${{ github.event.inputs.force }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
run: ./ci/resolve-version.sh
|
||||
|
||||
# 1. Checkout repo
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
persist-credentials: true
|
||||
fetch-depth: 0
|
||||
build:
|
||||
needs: check
|
||||
if: needs.check.outputs.build == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
VERSION: ${{ needs.check.outputs.version }}
|
||||
UPSTREAM_VERSION: ${{ needs.check.outputs.upstream }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# 2. Get latest Tailscale version
|
||||
- name: Get latest Tailscale version
|
||||
id: tailscale_version
|
||||
run: |
|
||||
# 1. Start from GitHub latest
|
||||
GH_TAG=$(curl -s https://api.github.com/repos/tailscale/tailscale/releases/latest | jq -r .tag_name)
|
||||
GH_VERSION=${GH_TAG#v}
|
||||
- name: Apply version and upstream pins
|
||||
run: ./ci/apply-version.sh "$VERSION" "$UPSTREAM_VERSION"
|
||||
|
||||
echo "GitHub latest: $GH_VERSION"
|
||||
- name: Build packages
|
||||
run: ./ci/build-packages.sh
|
||||
|
||||
# 2. See if static ARM build exists for that version
|
||||
if curl -sfI "https://pkgs.tailscale.com/stable/tailscale_${GH_VERSION}_arm.tgz" > /dev/null; then
|
||||
VERSION="$GH_VERSION"
|
||||
echo "Using GitHub latest (has ARM package): $VERSION"
|
||||
else
|
||||
echo "No ARM package for $GH_VERSION, falling back to latest version on pkgs.tailscale.com"
|
||||
# 3. Derive latest version that actually has an ARM tarball
|
||||
VERSION=$(
|
||||
curl -s https://pkgs.tailscale.com/stable/ \
|
||||
| grep -o 'tailscale_[0-9.]*_arm\.tgz' \
|
||||
| sed -E 's/^tailscale_([0-9.]+)_arm\.tgz$/\1/' \
|
||||
| sort -V | tail -n1
|
||||
)
|
||||
echo "Fallback version: $VERSION"
|
||||
fi
|
||||
|
||||
echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get current repo version
|
||||
id: current
|
||||
run: |
|
||||
CURRENT=$(find . -path "*/app/manifest.json" -exec jq -r '.acapPackageConf.setup.version' {} \; | sort -u | head -n1)
|
||||
echo "CURRENT_VERSION=$CURRENT" >> $GITHUB_ENV
|
||||
echo "Current repo version: $CURRENT"
|
||||
|
||||
- name: Compare versions
|
||||
id: compare
|
||||
run: |
|
||||
echo "Repo version: $CURRENT_VERSION"
|
||||
echo "Latest Tailscale version: $RELEASE_VERSION"
|
||||
echo "Trigger: ${{ github.event_name }}"
|
||||
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "build_needed=true" >> $GITHUB_ENV
|
||||
echo "Manual trigger — building regardless of version."
|
||||
elif [ "$CURRENT_VERSION" = "$RELEASE_VERSION" ]; then
|
||||
echo "build_needed=false" >> $GITHUB_ENV
|
||||
echo "Already up to date. Skipping build."
|
||||
else
|
||||
echo "build_needed=true" >> $GITHUB_ENV
|
||||
echo "New version detected. Will build."
|
||||
fi
|
||||
|
||||
# 3. Download Tailscale binaries
|
||||
- name: Download Tailscale binaries
|
||||
if: env.build_needed == 'true'
|
||||
run: |
|
||||
mkdir -p tailscale_bins
|
||||
curl -L "https://pkgs.tailscale.com/stable/tailscale_${RELEASE_VERSION}_arm.tgz" -o tailscale_arm.tgz
|
||||
tar -xzf tailscale_arm.tgz -C tailscale_bins --strip-components=1
|
||||
mv tailscale_bins/tailscale tailscale_bins/tailscale_arm
|
||||
mv tailscale_bins/tailscaled tailscale_bins/tailscaled_arm
|
||||
|
||||
curl -L "https://pkgs.tailscale.com/stable/tailscale_${RELEASE_VERSION}_arm64.tgz" -o tailscale_arm64.tgz
|
||||
tar -xzf tailscale_arm64.tgz -C tailscale_bins --strip-components=1
|
||||
mv tailscale_bins/tailscale tailscale_bins/tailscale_arm64
|
||||
mv tailscale_bins/tailscaled tailscale_bins/tailscaled_arm64
|
||||
|
||||
# 4. Strip binaries to reduce package size
|
||||
- name: Strip binaries
|
||||
if: env.build_needed == 'true'
|
||||
run: |
|
||||
# Install cross-architecture strip tools
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y binutils-aarch64-linux-gnu binutils-arm-linux-gnueabihf
|
||||
|
||||
# Strip debug info and symbol tables (zero runtime/memory cost)
|
||||
aarch64-linux-gnu-strip -s tailscale_bins/tailscale_arm64
|
||||
aarch64-linux-gnu-strip -s tailscale_bins/tailscaled_arm64
|
||||
arm-linux-gnueabihf-strip -s tailscale_bins/tailscale_arm
|
||||
arm-linux-gnueabihf-strip -s tailscale_bins/tailscaled_arm
|
||||
|
||||
ls -lh tailscale_bins/
|
||||
|
||||
# 5. Build each folder, update manifest, and copy .eap files
|
||||
- name: Build all folders
|
||||
if: env.build_needed == 'true'
|
||||
run: |
|
||||
mkdir -p build
|
||||
rm -rf releases
|
||||
mkdir -p releases
|
||||
|
||||
for folder in */ ; do
|
||||
FOLDER_NAME="${folder%/}" # remove trailing slash
|
||||
[[ "$FOLDER_NAME" == "common" ]] && continue
|
||||
[[ ! -d "$folder/app" ]] && continue
|
||||
echo "Processing folder $FOLDER_NAME"
|
||||
|
||||
# aarch64/arm/aarch64_ROOT/arm_ROOT share their C source, run script,
|
||||
# HTML, and Makefile via common/app/ (see Dockerfile COPY layers);
|
||||
# only arm_acap3 still carries its own self-contained app/ tree.
|
||||
case "$FOLDER_NAME" in
|
||||
aarch64|arm|aarch64_ROOT|arm_ROOT) APP_LIB_DIR="common/app/lib" ;;
|
||||
*) APP_LIB_DIR="$folder/app/lib" ;;
|
||||
esac
|
||||
mkdir -p "$APP_LIB_DIR"
|
||||
|
||||
# Detect architecture
|
||||
if [[ "$FOLDER_NAME" == arm* ]]; then
|
||||
cp tailscale_bins/tailscale_arm "$APP_LIB_DIR/tailscale"
|
||||
cp tailscale_bins/tailscaled_arm "$APP_LIB_DIR/tailscaled"
|
||||
else
|
||||
cp tailscale_bins/tailscale_arm64 "$APP_LIB_DIR/tailscale"
|
||||
cp tailscale_bins/tailscaled_arm64 "$APP_LIB_DIR/tailscaled"
|
||||
- name: Verify packages
|
||||
run: |
|
||||
set -euo pipefail
|
||||
shopt -s nullglob
|
||||
packages=(releases/*.eap)
|
||||
if [ ${#packages[@]} -eq 0 ]; then
|
||||
echo "no .eap produced" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Detect variant suffix for .eap naming
|
||||
if [[ "$FOLDER_NAME" == *_ROOT ]]; then
|
||||
VARIANT="_root"
|
||||
elif [[ "$FOLDER_NAME" == *_acap3 ]]; then
|
||||
VARIANT="_acap3"
|
||||
else
|
||||
VARIANT=""
|
||||
fi
|
||||
|
||||
# Update version — manifest.json for ACAP 4, package.conf for ACAP 3
|
||||
if [[ -f "$folder/app/manifest.json" ]]; then
|
||||
sed -i "s/\"version\": \".*\"/\"version\": \"${RELEASE_VERSION}\"/" "$folder/app/manifest.json"
|
||||
elif [[ -f "$folder/app/package.conf" ]]; then
|
||||
IFS='.' read -r MAJOR MINOR MICRO <<< "${RELEASE_VERSION}"
|
||||
sed -i "s/^APPMAJORVERSION=.*/APPMAJORVERSION=${MAJOR}/" "$folder/app/package.conf"
|
||||
sed -i "s/^APPMINORVERSION=.*/APPMINORVERSION=${MINOR}/" "$folder/app/package.conf"
|
||||
sed -i "s/^APPMICROVERSION=.*/APPMICROVERSION=${MICRO}/" "$folder/app/package.conf"
|
||||
fi
|
||||
|
||||
# Docker build
|
||||
TAG_NAME=$(echo "$FOLDER_NAME" | tr '[:upper:]' '[:lower:]' | tr '/ ' '_') # lowercase and clean
|
||||
echo "Building $TAG_NAME"
|
||||
docker build -f "$folder/Dockerfile" --tag "$TAG_NAME" .
|
||||
|
||||
# Extract .eap files into build folder
|
||||
EAP_OUTPUT="./build/${TAG_NAME}"
|
||||
mkdir -p "$EAP_OUTPUT"
|
||||
CID=$(docker create "$TAG_NAME")
|
||||
docker cp "$CID":/opt/app "$EAP_OUTPUT"
|
||||
docker rm "$CID" >/dev/null
|
||||
|
||||
# Move all .eap files to releases folder, append variant if needed
|
||||
find "$EAP_OUTPUT" -type f -name "*.eap" | while read eap; do
|
||||
BASENAME=$(basename "$eap" .eap)
|
||||
if [[ -n "$VARIANT" ]]; then
|
||||
mv "$eap" "releases/${BASENAME}${VARIANT}.eap"
|
||||
else
|
||||
mv "$eap" "releases/${BASENAME}.eap"
|
||||
fi
|
||||
for package in "${packages[@]}"; do
|
||||
echo "== $package"
|
||||
tar tzf "$package" >/dev/null
|
||||
done
|
||||
done
|
||||
|
||||
# Clean up
|
||||
rm -rf build tailscale_bins *.tgz
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: packages
|
||||
path: releases/*.eap
|
||||
if-no-files-found: error
|
||||
|
||||
# 6. Commit updated manifests and .eap files directly to main
|
||||
- name: Commit updates to main
|
||||
if: env.build_needed == 'true'
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
# Only commit manifests and ACAP 3 package.conf; do not track release artifacts
|
||||
git add */app/manifest.json arm_acap3/app/package.conf
|
||||
if git diff --cached --quiet; then
|
||||
echo "No changes to commit"
|
||||
else
|
||||
git commit -m "Update Tailscale to v${RELEASE_VERSION}"
|
||||
git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/Mo3he/Axis_Cam_Tailscale.git main
|
||||
fi
|
||||
# Only after a successful build, so a failed upstream jump leaves main clean.
|
||||
- name: Commit version bump
|
||||
if: needs.check.outputs.release == 'true' && github.event_name != 'pull_request'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add -A ':!releases'
|
||||
if git diff --cached --quiet; then
|
||||
echo "nothing to commit"
|
||||
exit 0
|
||||
fi
|
||||
git commit -m "Update to $VERSION"
|
||||
# The remote can move while a long build runs, so rebase and retry.
|
||||
for attempt in 1 2 3; do
|
||||
if git push; then
|
||||
exit 0
|
||||
fi
|
||||
echo "push rejected, rebasing (attempt $attempt)"
|
||||
git pull --rebase --autostash origin main
|
||||
done
|
||||
echo "could not push the version bump" >&2
|
||||
exit 1
|
||||
|
||||
# 7. Create GitHub Release with all new .eap files
|
||||
- name: Create GitHub Release
|
||||
if: env.build_needed == 'true'
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: v${{ env.RELEASE_VERSION }}
|
||||
name: "Tailscale VPN ${{ env.RELEASE_VERSION }}"
|
||||
files: releases/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
release:
|
||||
needs: [check, build]
|
||||
if: needs.check.outputs.release == 'true' && github.event_name != 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
VERSION: ${{ needs.check.outputs.version }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: packages
|
||||
path: releases
|
||||
|
||||
# Stays a DRAFT: unsigned packages must never reach users, and an
|
||||
# already-published release must never be overwritten with unsigned ones.
|
||||
- name: Create or refresh draft release
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tag="v$VERSION"
|
||||
./ci/release-notes.sh "$VERSION" "${{ needs.check.outputs.upstream }}" > /tmp/notes.md
|
||||
cat /tmp/notes.md
|
||||
if gh release view "$tag" --json isDraft --jq '.isDraft' 2>/dev/null | grep -qx true; then
|
||||
gh release upload "$tag" releases/*.eap --clobber
|
||||
gh release edit "$tag" --notes-file /tmp/notes.md
|
||||
elif gh release view "$tag" >/dev/null 2>&1; then
|
||||
echo "release $tag is already published; refusing to touch it" >&2
|
||||
exit 1
|
||||
else
|
||||
gh release create "$tag" releases/*.eap \
|
||||
--draft \
|
||||
--title "Tailscale VPN $VERSION" \
|
||||
--notes-file /tmp/notes.md
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env sh
|
||||
# Build the Tailscale ACAP variants.
|
||||
#
|
||||
# ./build.sh # build every variant
|
||||
# ./build.sh aarch64 arm # build only the named variant folders
|
||||
#
|
||||
# Downloads the prebuilt Tailscale binaries, strips them, then builds each
|
||||
# variant folder that contains an app/ directory. Variant folders map to the
|
||||
# .eap suffixes used in releases: *_ROOT -> _root, *_acap3 -> _acap3.
|
||||
#
|
||||
# Override the container runtime with RUNTIME=docker|podman.
|
||||
# TAILSCALE_VERSION pins the upstream binaries; it defaults to whatever
|
||||
# ci/upstream-version.sh resolves.
|
||||
set -eu
|
||||
|
||||
REPO_ROOT=$(cd -P "$(dirname "$0")" && pwd)
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
if [ -z "${RUNTIME:-}" ]; then
|
||||
if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
|
||||
RUNTIME=docker
|
||||
elif command -v podman >/dev/null 2>&1; then
|
||||
RUNTIME=podman
|
||||
else
|
||||
echo 'Error: neither docker nor podman found in PATH' >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo "==> Using container runtime: ${RUNTIME}"
|
||||
|
||||
VERSION="${TAILSCALE_VERSION:-$(sh ci/upstream-version.sh)}"
|
||||
[ -n "$VERSION" ] || { echo 'Error: could not resolve a Tailscale version' >&2; exit 1; }
|
||||
echo "==> Tailscale version: ${VERSION}"
|
||||
|
||||
# --- fetch and strip upstream binaries ---------------------------------------
|
||||
|
||||
BINS="${REPO_ROOT}/tailscale_bins"
|
||||
rm -rf "$BINS"
|
||||
mkdir -p "$BINS"
|
||||
|
||||
fetch_arch() {
|
||||
tgz_arch=$1
|
||||
suffix=$2
|
||||
echo "==> Downloading tailscale ${VERSION} (${tgz_arch})"
|
||||
curl -fsSL "https://pkgs.tailscale.com/stable/tailscale_${VERSION}_${tgz_arch}.tgz" \
|
||||
-o "${BINS}/ts_${suffix}.tgz"
|
||||
tar -xzf "${BINS}/ts_${suffix}.tgz" -C "$BINS" --strip-components=1
|
||||
mv "${BINS}/tailscale" "${BINS}/tailscale_${suffix}"
|
||||
mv "${BINS}/tailscaled" "${BINS}/tailscaled_${suffix}"
|
||||
rm -f "${BINS}/ts_${suffix}.tgz"
|
||||
}
|
||||
|
||||
fetch_arch arm arm
|
||||
fetch_arch arm64 arm64
|
||||
|
||||
# Stripping is optional: it only shrinks the package, so a missing cross
|
||||
# binutils on a dev machine must not fail the build.
|
||||
strip_with() {
|
||||
tool=$1
|
||||
suffix=$2
|
||||
command -v "$tool" >/dev/null 2>&1 || return 0
|
||||
"$tool" -s "${BINS}/tailscale_${suffix}" || true
|
||||
"$tool" -s "${BINS}/tailscaled_${suffix}" || true
|
||||
}
|
||||
strip_with aarch64-linux-gnu-strip arm64
|
||||
strip_with arm-linux-gnueabihf-strip arm
|
||||
|
||||
# --- build variants -----------------------------------------------------------
|
||||
|
||||
echo '==> Cleaning old .eap files...'
|
||||
rm -f "${REPO_ROOT}"/*.eap
|
||||
rm -rf "${REPO_ROOT}/build"
|
||||
|
||||
build_variant() {
|
||||
folder=${1%/}
|
||||
[ -d "${folder}/app" ] || return 0
|
||||
[ "$folder" = common ] && return 0
|
||||
|
||||
# aarch64/arm/aarch64_ROOT/arm_ROOT share sources via common/app; only
|
||||
# arm_acap3 carries its own self-contained app tree.
|
||||
case "$folder" in
|
||||
aarch64 | arm | aarch64_ROOT | arm_ROOT) lib_dir="common/app/lib" ;;
|
||||
*) lib_dir="${folder}/app/lib" ;;
|
||||
esac
|
||||
mkdir -p "$lib_dir"
|
||||
|
||||
case "$folder" in
|
||||
arm*) src=arm ;;
|
||||
*) src=arm64 ;;
|
||||
esac
|
||||
cp "${BINS}/tailscale_${src}" "${lib_dir}/tailscale"
|
||||
cp "${BINS}/tailscaled_${src}" "${lib_dir}/tailscaled"
|
||||
|
||||
case "$folder" in
|
||||
*_ROOT) variant="_root" ;;
|
||||
*_acap3) variant="_acap3" ;;
|
||||
*) variant="" ;;
|
||||
esac
|
||||
|
||||
tag=$(echo "$folder" | tr '[:upper:]' '[:lower:]' | tr '/ ' '__')
|
||||
echo "==> Building ${folder}"
|
||||
"$RUNTIME" build -f "${folder}/Dockerfile" --tag "$tag" .
|
||||
|
||||
out="${REPO_ROOT}/build/${tag}"
|
||||
mkdir -p "$out"
|
||||
cid=$("$RUNTIME" create "$tag")
|
||||
"$RUNTIME" cp "${cid}:/opt/app" "$out"
|
||||
"$RUNTIME" rm "$cid" >/dev/null
|
||||
|
||||
find "$out" -type f -name '*.eap' | while read -r eap; do
|
||||
base=$(basename "$eap" .eap)
|
||||
mv "$eap" "${REPO_ROOT}/${base}${variant}.eap"
|
||||
done
|
||||
}
|
||||
|
||||
if [ "$#" -eq 0 ]; then
|
||||
set -- */
|
||||
fi
|
||||
for v in "$@"; do
|
||||
build_variant "$v"
|
||||
done
|
||||
|
||||
rm -rf "${REPO_ROOT}/build" "$BINS"
|
||||
|
||||
echo '==> Done!'
|
||||
ls -lh "${REPO_ROOT}"/*.eap 2>/dev/null || true
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Write a version into every place this repo records it and refresh the
|
||||
# upstream pins declared in .acap.json.
|
||||
#
|
||||
# Usage: ci/apply-version.sh <version> [upstream-version]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
VERSION=${1:?version required}
|
||||
UPSTREAM=${2:-}
|
||||
|
||||
CONFIG=.acap.json
|
||||
cfg() { jq -r "$1" "$CONFIG"; }
|
||||
|
||||
IFS='.' read -r MAJOR MINOR MICRO <<<"$VERSION"
|
||||
|
||||
while IFS= read -r manifest; do
|
||||
[ -n "$manifest" ] || continue
|
||||
tmp=$(mktemp)
|
||||
jq --arg v "$VERSION" '.acapPackageConf.setup.version = $v' "$manifest" >"$tmp"
|
||||
mv "$tmp" "$manifest"
|
||||
echo "version $VERSION -> $manifest"
|
||||
done < <(find . -path '*/app/manifest.json' -not -path './node_modules/*' | sort)
|
||||
|
||||
while IFS= read -r conf; do
|
||||
[ -n "$conf" ] || continue
|
||||
sed -i.bak -E \
|
||||
-e "s/^APPMAJORVERSION=.*/APPMAJORVERSION=${MAJOR}/" \
|
||||
-e "s/^APPMINORVERSION=.*/APPMINORVERSION=${MINOR}/" \
|
||||
-e "s/^APPMICROVERSION=.*/APPMICROVERSION=${MICRO}/" \
|
||||
-e "s/^VERSION=.*/VERSION=${VERSION}/" \
|
||||
"$conf"
|
||||
rm -f "$conf.bak"
|
||||
echo "version $VERSION -> $conf"
|
||||
done < <(find . -path '*/app/package.conf' | sort)
|
||||
|
||||
pin_count=$(cfg '.pins | length')
|
||||
for ((i = 0; i < pin_count; i++)); do
|
||||
file=$(cfg ".pins[$i].file")
|
||||
arg=$(cfg ".pins[$i].arg")
|
||||
prefix=$(cfg ".pins[$i].prefix // empty")
|
||||
sha_url=$(cfg ".pins[$i].sha256Url // empty")
|
||||
[ -f "$file" ] || {
|
||||
echo "pin target missing: $file" >&2
|
||||
continue
|
||||
}
|
||||
|
||||
if [ -n "$sha_url" ]; then
|
||||
# Checksum pins track the version pin, so the tarball is fetched and
|
||||
# hashed rather than substituted.
|
||||
url=${sha_url//\$\{VERSION\}/${UPSTREAM:-$VERSION}}
|
||||
echo "hashing $url"
|
||||
value=$(curl -fsSL "$url" | sha256sum | awk '{print $1}')
|
||||
else
|
||||
value="${prefix}${UPSTREAM:-$VERSION}"
|
||||
fi
|
||||
|
||||
sed -i.bak -E "s|^ARG ${arg}=.*|ARG ${arg}=${value}|" "$file"
|
||||
rm -f "$file.bak"
|
||||
echo "pin ${arg}=${value} -> $file"
|
||||
done
|
||||
|
||||
module=$(cfg '.upstream.module // empty')
|
||||
gomod=$(cfg '.upstream.goMod // empty')
|
||||
if [ -n "$module" ] && [ -n "$UPSTREAM" ] && [ -f "$gomod" ]; then
|
||||
(cd "$(dirname "$gomod")" && go get "${module}@${UPSTREAM}" && go mod tidy)
|
||||
echo "go module ${module}@${UPSTREAM}"
|
||||
fi
|
||||
|
||||
if [ -f CHANGELOG.md ] && ! grep -qE "^## \[?${VERSION}\]?" CHANGELOG.md; then
|
||||
first_heading=$(grep -n -m1 '^## ' CHANGELOG.md | cut -d: -f1 || true)
|
||||
tmp=$(mktemp)
|
||||
{
|
||||
if [ -n "$first_heading" ]; then
|
||||
head -n "$((first_heading - 1))" CHANGELOG.md
|
||||
else
|
||||
cat CHANGELOG.md
|
||||
echo
|
||||
fi
|
||||
echo "## ${VERSION} - $(date +%Y-%m-%d)"
|
||||
echo
|
||||
if [ -n "$UPSTREAM" ]; then
|
||||
echo "- Update to upstream ${UPSTREAM}."
|
||||
else
|
||||
echo "- Release ${VERSION}."
|
||||
fi
|
||||
echo
|
||||
[ -n "$first_heading" ] && tail -n +"$first_heading" CHANGELOG.md
|
||||
} >"$tmp"
|
||||
mv "$tmp" CHANGELOG.md
|
||||
echo "changelog entry added for $VERSION"
|
||||
fi
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Run the repo's build and collect every .eap into releases/.
|
||||
# The build command and extra env come from .acap.json.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
CONFIG=.acap.json
|
||||
cfg() { jq -r "$1" "$CONFIG"; }
|
||||
|
||||
COMMAND=$(cfg '.build.command')
|
||||
|
||||
while IFS=$'\t' read -r key value; do
|
||||
[ -n "$key" ] || continue
|
||||
value=${value//\$\{VERSION\}/${VERSION:-}}
|
||||
export "$key=$value"
|
||||
echo "env $key=$value"
|
||||
done < <(cfg '.build.env | to_entries[]? | [.key, .value] | @tsv')
|
||||
|
||||
rm -rf releases
|
||||
mkdir -p releases
|
||||
|
||||
echo "== $COMMAND"
|
||||
eval "$COMMAND"
|
||||
|
||||
# Repos drop packages in the root, build/, build_<arch>/ or releases/ depending
|
||||
# on the repo, so collect from anywhere except releases/ itself.
|
||||
found=0
|
||||
while IFS= read -r package; do
|
||||
[ -n "$package" ] || continue
|
||||
mv "$package" releases/
|
||||
found=$((found + 1))
|
||||
done < <(find . -name '*.eap' -not -path './releases/*' -not -path './.git/*')
|
||||
|
||||
[ "$found" -gt 0 ] || {
|
||||
echo "no .eap produced" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "collected $found package(s):"
|
||||
ls -lh releases/
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Generate release notes for a draft release.
|
||||
#
|
||||
# Usage: ci/release-notes.sh <version> [upstream-version] > notes.md
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
VERSION=${1:?version required}
|
||||
UPSTREAM=${2:-}
|
||||
|
||||
CONFIG=.acap.json
|
||||
cfg() { jq -r "$1" "$CONFIG"; }
|
||||
|
||||
FRIENDLY=$(cfg '.friendlyName')
|
||||
UPSTREAM_NAME=$(cfg '.upstream.name // .upstream.repo // .upstream.module // empty')
|
||||
CHANGES_URL=$(cfg '.upstream.changesUrl // empty')
|
||||
CHANGES_URL=${CHANGES_URL//\$\{UPSTREAM\}/$UPSTREAM}
|
||||
|
||||
# Previous tag, so the compare link points somewhere useful.
|
||||
PREVIOUS=$(git tag --list 'v*' --sort=-v:refname | grep -v "^v${VERSION}$" | head -1 || true)
|
||||
|
||||
printf '%s %s\n\n' "$FRIENDLY" "$VERSION"
|
||||
|
||||
if [ -n "$UPSTREAM" ] && [ -n "$UPSTREAM_NAME" ]; then
|
||||
printf 'Packages **%s `%s`**.\n\n' "$UPSTREAM_NAME" "$UPSTREAM"
|
||||
fi
|
||||
|
||||
if [ -n "$CHANGES_URL" ]; then
|
||||
printf '### Upstream changes\n\n%s\n\n' "$CHANGES_URL"
|
||||
fi
|
||||
|
||||
if [ -f CHANGELOG.md ]; then
|
||||
# Pull just this version's section out of the changelog.
|
||||
section=$(awk -v v="$VERSION" '
|
||||
$0 ~ "^## \\[?" v "\\]?" { found = 1; next }
|
||||
found && /^## / { exit }
|
||||
found { print }
|
||||
' CHANGELOG.md | sed '/^[[:space:]]*$/d')
|
||||
if [ -n "$section" ]; then
|
||||
printf '### Changes\n\n%s\n\n' "$section"
|
||||
fi
|
||||
fi
|
||||
|
||||
cat <<'EOF'
|
||||
### Packages
|
||||
|
||||
Install the `signed_*.eap` matching your device architecture. Packages ending
|
||||
`_acap3` or `_root` are published unsigned by design: ACAP 3 packages use
|
||||
manifest schema 1.x, which the Axis signing service does not accept.
|
||||
|
||||
EOF
|
||||
|
||||
if [ -n "$PREVIOUS" ] && [ -n "${GITHUB_REPOSITORY:-}" ]; then
|
||||
printf '**Full changelog**: https://github.com/%s/compare/%s...v%s\n' \
|
||||
"$GITHUB_REPOSITORY" "$PREVIOUS" "$VERSION"
|
||||
fi
|
||||
Executable
+144
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Decide which version this repo should build, from .acap.json.
|
||||
# Writes build/release/version/upstream to GITHUB_OUTPUT under CI, and always
|
||||
# prints the decision so it can be run locally to preview.
|
||||
#
|
||||
# Policies:
|
||||
# mirror the ACAP version follows the upstream version exactly.
|
||||
# patch upstream is tracked through a pin; our own last digit is bumped.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
CONFIG=.acap.json
|
||||
[ -f "$CONFIG" ] || {
|
||||
echo "missing $CONFIG" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cfg() { jq -r "$1" "$CONFIG"; }
|
||||
|
||||
POLICY=$(cfg '.versionPolicy')
|
||||
UPSTREAM_TYPE=$(cfg '.upstream.type')
|
||||
EVENT_NAME=${EVENT_NAME:-manual}
|
||||
INPUT_VERSION=${INPUT_VERSION:-}
|
||||
INPUT_FORCE=${INPUT_FORCE:-false}
|
||||
|
||||
current_version() {
|
||||
local manifest conf
|
||||
manifest=$(find . -path '*/app/manifest.json' -not -path './node_modules/*' | sort | head -1)
|
||||
if [ -n "$manifest" ]; then
|
||||
jq -r '.acapPackageConf.setup.version' "$manifest"
|
||||
return
|
||||
fi
|
||||
conf=$(find . -path '*/app/package.conf' | sort | head -1)
|
||||
[ -n "$conf" ] && sed -n 's/^VERSION=//p' "$conf" | head -1
|
||||
}
|
||||
|
||||
# Current value of the first pin, used by "patch" to detect upstream movement.
|
||||
pin_value() {
|
||||
local file arg gomod module
|
||||
file=$(cfg '.pins[0].file // empty')
|
||||
arg=$(cfg '.pins[0].arg // empty')
|
||||
if [ -n "$file" ] && [ -n "$arg" ] && [ -f "$file" ]; then
|
||||
sed -n "s/^ARG ${arg}=//p" "$file" | head -1
|
||||
return
|
||||
fi
|
||||
gomod=$(cfg '.upstream.goMod // empty')
|
||||
module=$(cfg '.upstream.module // empty')
|
||||
if [ -n "$gomod" ] && [ -f "$gomod" ]; then
|
||||
# The module may appear as "require mod ver" or as "mod ver" inside a
|
||||
# require block, so take the field after the module name wherever it is.
|
||||
awk -v m="$module" '{ for (i = 1; i < NF; i++) if ($i == m) { print $(i + 1); exit } }' "$gomod"
|
||||
fi
|
||||
}
|
||||
|
||||
# FFmpeg and openvpn3 publish no releases, and their tag lists contain names
|
||||
# that are not versions, hence the explicit pattern per repo.
|
||||
upstream_version() {
|
||||
case "$UPSTREAM_TYPE" in
|
||||
github-release)
|
||||
local tag
|
||||
tag=$(gh api "repos/$(cfg '.upstream.repo')/releases/latest" --jq '.tag_name')
|
||||
[ "$(cfg '.upstream.stripV // false')" = true ] && tag=${tag#v}
|
||||
printf '%s\n' "$tag"
|
||||
;;
|
||||
github-tag)
|
||||
gh api "repos/$(cfg '.upstream.repo')/tags?per_page=100" --paginate --jq '.[].name' |
|
||||
grep -E "$(cfg '.upstream.tagPattern')" |
|
||||
sed "s|^$(cfg '.upstream.strip // empty')||" |
|
||||
sort -V | tail -1
|
||||
;;
|
||||
go-module)
|
||||
curl -fsSL "https://proxy.golang.org/$(cfg '.upstream.module')/@latest" | jq -r '.Version'
|
||||
;;
|
||||
script)
|
||||
bash "$(cfg '.upstream.script')"
|
||||
;;
|
||||
*)
|
||||
echo ''
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
bump_patch() {
|
||||
local major minor patch
|
||||
IFS='.' read -r major minor patch <<<"$1"
|
||||
printf '%s.%s.%s\n' "${major:-0}" "${minor:-0}" "$((${patch:-0} + 1))"
|
||||
}
|
||||
|
||||
CURRENT=$(current_version)
|
||||
UPSTREAM=$(upstream_version || true)
|
||||
|
||||
BUILD=false
|
||||
RELEASE=false
|
||||
TARGET="$CURRENT"
|
||||
|
||||
if [ -n "$INPUT_VERSION" ]; then
|
||||
TARGET=${INPUT_VERSION#v}
|
||||
BUILD=true
|
||||
RELEASE=true
|
||||
elif [ "$POLICY" = mirror ]; then
|
||||
if [ -n "$UPSTREAM" ] && [ "$UPSTREAM" != "$CURRENT" ]; then
|
||||
TARGET="$UPSTREAM"
|
||||
BUILD=true
|
||||
RELEASE=true
|
||||
fi
|
||||
elif [ "$POLICY" = patch ]; then
|
||||
if [ -n "$UPSTREAM" ] && [ "$UPSTREAM" != "$(pin_value)" ]; then
|
||||
TARGET=$(bump_patch "$CURRENT")
|
||||
BUILD=true
|
||||
RELEASE=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Pull requests build for validation but never release.
|
||||
if [ "$EVENT_NAME" = pull_request ]; then
|
||||
BUILD=true
|
||||
RELEASE=false
|
||||
fi
|
||||
|
||||
if [ "$INPUT_FORCE" = true ]; then
|
||||
BUILD=true
|
||||
RELEASE=true
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
policy : $POLICY
|
||||
current : $CURRENT
|
||||
upstream : ${UPSTREAM:-n/a}
|
||||
target : $TARGET
|
||||
build : $BUILD
|
||||
release : $RELEASE
|
||||
EOF
|
||||
|
||||
if [ -n "${GITHUB_OUTPUT:-}" ]; then
|
||||
{
|
||||
echo "build=$BUILD"
|
||||
echo "release=$RELEASE"
|
||||
echo "version=$TARGET"
|
||||
echo "upstream=$UPSTREAM"
|
||||
} >>"$GITHUB_OUTPUT"
|
||||
fi
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env sh
|
||||
# Resolve the Tailscale version to package.
|
||||
#
|
||||
# Tailscale's GitHub "latest" release sometimes lands before the static ARM
|
||||
# tarballs are published, so fall back to the newest version that actually has
|
||||
# an ARM package on pkgs.tailscale.com.
|
||||
set -eu
|
||||
|
||||
GH_VERSION=$(curl -fsS https://api.github.com/repos/tailscale/tailscale/releases/latest \
|
||||
| sed -n 's/.*"tag_name": *"v\{0,1\}\([^"]*\)".*/\1/p' | head -1)
|
||||
|
||||
if [ -n "${GH_VERSION}" ] && \
|
||||
curl -sfI "https://pkgs.tailscale.com/stable/tailscale_${GH_VERSION}_arm.tgz" >/dev/null 2>&1; then
|
||||
printf '%s\n' "${GH_VERSION}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
curl -fsS https://pkgs.tailscale.com/stable/ \
|
||||
| grep -o 'tailscale_[0-9.]*_arm\.tgz' \
|
||||
| sed -E 's/^tailscale_([0-9.]+)_arm\.tgz$/\1/' \
|
||||
| sort -V | tail -1
|
||||
Reference in New Issue
Block a user