Compare commits

...
21 Commits
Author SHA1 Message Date
github-actions[bot] 3413d3a992 Update to 1.102.4 2026-09-11 09:27:07 +00:00
Weston Blieden f526196d99 Sync apply-version.sh: apply go.mod pins declared in .acap.json 2026-08-22 13:32:27 +02:00
Weston Blieden 14518ffe16 Strip inside the SDK container so every build matches CI
Stripping relied on host cross-binutils and was best-effort, so a machine without
them silently shipped a 68 MB package instead of 45 MB under the same version
number. Running the SDK's $STRIP in a container removes the host dependency.
Uses create/cp rather than a bind mount because podman machine on macOS only
mounts $HOME.
2026-08-22 13:22:01 +02:00
Weston Blieden d615191fc2 Count packages a build writes straight into releases/ 2026-08-22 08:27:52 +02:00
Weston Blieden d2f2cd13a4 Keep the UI update check in sync with the packaged version 2026-08-21 15:48:39 +02:00
Weston Blieden 96f5a7c909 Group Dependabot action updates into a single PR 2026-08-21 15:27:23 +02:00
Weston Blieden 86309e1291 Bump GitHub Actions to checkout v7, upload-artifact v7, download-artifact v8 2026-08-21 15:18:29 +02:00
Weston Blieden ed0f6d23fe Add Dependabot; never target an already-published version 2026-08-21 14:50:11 +02:00
Weston Blieden 474d783f48 Add Dependabot for GitHub Actions 2026-08-21 14:47:47 +02:00
Weston Blieden 9c1bec5043 Mirror upstream only when it is ahead of our version line 2026-08-21 14:37:23 +02:00
Weston Blieden 15ac2c3395 Only mention unsigned variants a release actually ships 2026-08-21 13:58:25 +02:00
Weston Blieden 437f7f9e40 Keep build output out of the version bump commit 2026-08-21 13:24:33 +02:00
Weston Blieden 53240ed2ec Add upstream-tracking release pipeline; extract build.sh 2026-08-21 13:07:28 +02:00
github-actions[bot] 492392d175 Update Tailscale to v1.102.3 2026-08-21 01:59:18 +00:00
github-actions[bot] e17549fef0 Update Tailscale to v1.102.2 2026-08-05 03:36:20 +00:00
github-actions[bot] 3a71aba342 Update Tailscale to v1.102.1 2026-08-04 03:35:42 +00:00
github-actions[bot] dce8beaa1b Update Tailscale to v1.98.10 2026-07-29 06:32:18 +00:00
Weston Blieden b70d664f91 fix: use Mo3he vendor name in ROOT manifests to pass schema validation 2026-07-29 08:15:03 +02:00
Weston Blieden 9ea88ccd0d docs: extend verified OS range to 13 (intro + roadmap) 2026-07-24 13:06:32 +02:00
Weston Blieden f79f631d95 docs: note verified on AXIS OS 13 (13.0.0, aarch64) 2026-07-24 13:03:13 +02:00
Weston Blieden 835ccff82f docs: add Advertise Routes (Subnet Router) to README config table 2026-07-21 19:44:51 +02:00
18 changed files with 988 additions and 423 deletions
+22
View File
@@ -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"
]
}
}
+17
View File
@@ -0,0 +1,17 @@
---
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
# One PR per repo instead of one per action; the workflow is generated from
# Axis_Cam_Template/ci/build.yml.tmpl, so apply bumps there and re-sync.
groups:
github-actions:
patterns:
- "*"
commit-message:
prefix: ci
labels:
- dependencies
+142 -188
View File
@@ -1,205 +1,159 @@
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@v7
- 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@v7
# 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@v7
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
# Unstripped binaries for symbolising a crash from a shipped (stripped)
# package. Not a release asset: they are only useful while debugging.
- uses: actions/upload-artifact@v7
with:
name: debug-symbols
path: debug/
if-no-files-found: ignore
# 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 }}
# 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"
# Packages are already uploaded as an artifact; removing them here
# keeps build output out of the commit regardless of .gitignore.
rm -rf releases
git add -A
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
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@v7
with:
ref: main
fetch-depth: 0
- uses: actions/download-artifact@v8
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
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
+3
View File
@@ -6,6 +6,9 @@ releases/
build/
*.eap
# Unstripped binaries kept for crash symbolisation, uploaded as a CI artifact
debug/
# Do not track downloaded Tailscale tarballs and temp bins
tailscale_bins/
*.tgz
+4
View File
@@ -5,6 +5,10 @@ links to its full release notes on GitHub.
The format is based on [Keep a Changelog](https://keepachangelog.com/).
## 1.102.4 - 2026-09-11
- Update to upstream 1.102.4.
## [1.98.9-Signed] - 2026-07-21 - Tailscale VPN 1.98.9 (Signed)
- Packages are now signed with the Axis ACAP signing service and install
+5 -2
View File
@@ -46,7 +46,7 @@ lightweight WireGuard-based tunnel.
- Secure remote access to cameras.
- Easy to install via EAP package.
- Works on **AXIS OS 10.12+** (non-root version, verified across 10.12–12.10).
- Works on **AXIS OS 10.12+** (non-root version, verified across 10.12–13).
- Works on **legacy AXIS OS 9.x / 10.x** via the ACAP 3 variant.
- Based on **WireGuard VPN** technology.
@@ -71,6 +71,8 @@ access for third-party applications). Learn more:
> root access for ACAPs). Use the **ACAP 3** build only on legacy cameras that
> don't support ACAP 4 (AXIS OS 9–10).
**Verified on AXIS OS 13** (13.0.0, aarch64).
## Installation
> **Signed packages:** Release `.eap` files are signed with the Axis ACAP
@@ -116,6 +118,7 @@ take effect immediately:
| SOCKS5 Proxy Port | `1080` | Port for the outbound SOCKS5 proxy. |
| Accept DNS | `off` | Passes `--accept-dns=true` to `tailscale up`. Allows the tailnet to push DNS settings to the camera. Not available on `armv7hf_acap3`. |
| Accept Routes | `off` | Passes `--accept-routes=true` to `tailscale up`. Allows the camera to use subnet routes advertised by other nodes. Not available on `armv7hf_acap3`. |
| Advertise Routes (Subnet Router) | *(empty)* | Comma-separated CIDRs (e.g. `192.168.1.0/24,10.0.0.0/8`) this camera will route for the tailnet, turning it into a subnet router. Approve the routes in the Tailscale admin console after saving. Leave blank to disable. |
## Ports & security
@@ -218,7 +221,7 @@ announcement for details.
supports root third-party apps.
- [x] **Migrate to Manifest Schema v2** - Done for `aarch64`/`armv7hf` (schema
2.0.0, `compatibleOsVersions` declared); verified installability on OS
10.12–12.10.
10.12–13.
- [x] **Audit for executable stack usage** - All compiled binaries report
`flags rw-` (no executable stack) on every architecture and variant.
- [x] **Verify web UI works over HTTPS** - Verified live; the UI only issues
+63 -63
View File
@@ -1,67 +1,67 @@
{
"schemaVersion": "2.0.0",
"acapPackageConf": {
"setup": {
"appName": "Tailscale_VPN",
"friendlyName": "Tailscale VPN",
"vendor": "moshe@mohome.net",
"vendorId": "70ee172dd9",
"vendorUrl": "https://github.com/Mo3he/Axis_Cam_Tailscale",
"version": "1.98.9",
"architecture": "aarch64",
"runMode": "respawn",
"compatibleOsVersions": [
{
"max": "13"
}
]
},
"configuration": {
"settingPage": "index.html",
"reverseProxy": [
{
"apiPath": "api",
"target": "http://localhost:2201/",
"access": "admin"
}
],
"paramConfig": [
{
"name": "CustomServer",
"default": "",
"type": "string"
},
{
"name": "AuthKey",
"default": "",
"type": "string"
},
{
"name": "HttpProxyPort",
"default": "8080",
"type": "string"
},
{
"name": "Socks5Port",
"default": "1080",
"type": "string"
},
{
"name": "AcceptDNS",
"default": "false",
"type": "string"
},
{
"name": "AcceptRoutes",
"default": "false",
"type": "string"
},
{
"name": "AdvertiseRoutes",
"default": "",
"type": "string"
}
]
"schemaVersion": "2.0.0",
"acapPackageConf": {
"setup": {
"appName": "Tailscale_VPN",
"friendlyName": "Tailscale VPN",
"vendor": "moshe@mohome.net",
"vendorId": "70ee172dd9",
"vendorUrl": "https://github.com/Mo3he/Axis_Cam_Tailscale",
"version": "1.102.4",
"architecture": "aarch64",
"runMode": "respawn",
"compatibleOsVersions": [
{
"max": "13"
}
]
},
"configuration": {
"settingPage": "index.html",
"reverseProxy": [
{
"apiPath": "api",
"target": "http://localhost:2201/",
"access": "admin"
}
],
"paramConfig": [
{
"name": "CustomServer",
"default": "",
"type": "string"
},
{
"name": "AuthKey",
"default": "",
"type": "string"
},
{
"name": "HttpProxyPort",
"default": "8080",
"type": "string"
},
{
"name": "Socks5Port",
"default": "1080",
"type": "string"
},
{
"name": "AcceptDNS",
"default": "false",
"type": "string"
},
{
"name": "AcceptRoutes",
"default": "false",
"type": "string"
},
{
"name": "AdvertiseRoutes",
"default": "",
"type": "string"
}
]
}
}
}
+52 -52
View File
@@ -1,56 +1,56 @@
{
"schemaVersion": "1.7.0",
"acapPackageConf": {
"setup": {
"appName": "Tailscale_VPN",
"friendlyName": "Tailscale VPN",
"vendor": "moshe@mohome.net",
"embeddedSdkVersion": "3.0",
"user": {
"group": "root",
"username": "root"
},
"vendorUrl": "https://github.com/Mo3he/Axis_Cam_Tailscale",
"runMode": "respawn",
"version": "1.98.9",
"architecture": "aarch64"
},
"configuration": {
"settingPage": "index.html",
"reverseProxy": [
{
"apiPath": "api",
"target": "http://localhost:2201/",
"access": "admin"
}
],
"paramConfig": [
{
"name": "CustomServer",
"default": "",
"type": "string"
},
{
"name": "AuthKey",
"default": "",
"type": "string"
},
{
"name": "AcceptDNS",
"default": "false",
"type": "string"
},
{
"name": "AcceptRoutes",
"default": "false",
"type": "string"
},
{
"name": "AdvertiseRoutes",
"default": "",
"type": "string"
}
]
"schemaVersion": "1.7.0",
"acapPackageConf": {
"setup": {
"appName": "Tailscale_VPN",
"friendlyName": "Tailscale VPN",
"vendor": "Mo3he",
"embeddedSdkVersion": "3.0",
"user": {
"group": "root",
"username": "root"
},
"vendorUrl": "https://github.com/Mo3he/Axis_Cam_Tailscale",
"runMode": "respawn",
"version": "1.102.4",
"architecture": "aarch64"
},
"configuration": {
"settingPage": "index.html",
"reverseProxy": [
{
"apiPath": "api",
"target": "http://localhost:2201/",
"access": "admin"
}
],
"paramConfig": [
{
"name": "CustomServer",
"default": "",
"type": "string"
},
{
"name": "AuthKey",
"default": "",
"type": "string"
},
{
"name": "AcceptDNS",
"default": "false",
"type": "string"
},
{
"name": "AcceptRoutes",
"default": "false",
"type": "string"
},
{
"name": "AdvertiseRoutes",
"default": "",
"type": "string"
}
]
}
}
}
+63 -63
View File
@@ -1,67 +1,67 @@
{
"schemaVersion": "2.0.0",
"acapPackageConf": {
"setup": {
"appName": "Tailscale_VPN",
"friendlyName": "Tailscale VPN",
"vendor": "moshe@mohome.net",
"vendorId": "70ee172dd9",
"vendorUrl": "https://github.com/Mo3he/Axis_Cam_Tailscale",
"version": "1.98.9",
"architecture": "armv7hf",
"runMode": "respawn",
"compatibleOsVersions": [
{
"max": "13"
}
]
},
"configuration": {
"settingPage": "index.html",
"reverseProxy": [
{
"apiPath": "api",
"target": "http://localhost:2201/",
"access": "admin"
}
],
"paramConfig": [
{
"name": "CustomServer",
"default": "",
"type": "string"
},
{
"name": "AuthKey",
"default": "",
"type": "string"
},
{
"name": "HttpProxyPort",
"default": "8080",
"type": "string"
},
{
"name": "Socks5Port",
"default": "1080",
"type": "string"
},
{
"name": "AcceptDNS",
"default": "false",
"type": "string"
},
{
"name": "AcceptRoutes",
"default": "false",
"type": "string"
},
{
"name": "AdvertiseRoutes",
"default": "",
"type": "string"
}
]
"schemaVersion": "2.0.0",
"acapPackageConf": {
"setup": {
"appName": "Tailscale_VPN",
"friendlyName": "Tailscale VPN",
"vendor": "moshe@mohome.net",
"vendorId": "70ee172dd9",
"vendorUrl": "https://github.com/Mo3he/Axis_Cam_Tailscale",
"version": "1.102.4",
"architecture": "armv7hf",
"runMode": "respawn",
"compatibleOsVersions": [
{
"max": "13"
}
]
},
"configuration": {
"settingPage": "index.html",
"reverseProxy": [
{
"apiPath": "api",
"target": "http://localhost:2201/",
"access": "admin"
}
],
"paramConfig": [
{
"name": "CustomServer",
"default": "",
"type": "string"
},
{
"name": "AuthKey",
"default": "",
"type": "string"
},
{
"name": "HttpProxyPort",
"default": "8080",
"type": "string"
},
{
"name": "Socks5Port",
"default": "1080",
"type": "string"
},
{
"name": "AcceptDNS",
"default": "false",
"type": "string"
},
{
"name": "AcceptRoutes",
"default": "false",
"type": "string"
},
{
"name": "AdvertiseRoutes",
"default": "",
"type": "string"
}
]
}
}
}
+52 -52
View File
@@ -1,56 +1,56 @@
{
"schemaVersion": "1.7.0",
"acapPackageConf": {
"setup": {
"appName": "Tailscale_VPN",
"friendlyName": "Tailscale VPN",
"vendor": "moshe@mohome.net",
"embeddedSdkVersion": "3.0",
"user": {
"group": "root",
"username": "root"
},
"vendorUrl": "https://github.com/Mo3he/Axis_Cam_Tailscale",
"runMode": "respawn",
"version": "1.98.9",
"architecture": "armv7hf"
},
"configuration": {
"settingPage": "index.html",
"reverseProxy": [
{
"apiPath": "api",
"target": "http://localhost:2201/",
"access": "admin"
}
],
"paramConfig": [
{
"name": "CustomServer",
"default": "",
"type": "string"
},
{
"name": "AuthKey",
"default": "",
"type": "string"
},
{
"name": "AcceptDNS",
"default": "false",
"type": "string"
},
{
"name": "AcceptRoutes",
"default": "false",
"type": "string"
},
{
"name": "AdvertiseRoutes",
"default": "",
"type": "string"
}
]
"schemaVersion": "1.7.0",
"acapPackageConf": {
"setup": {
"appName": "Tailscale_VPN",
"friendlyName": "Tailscale VPN",
"vendor": "Mo3he",
"embeddedSdkVersion": "3.0",
"user": {
"group": "root",
"username": "root"
},
"vendorUrl": "https://github.com/Mo3he/Axis_Cam_Tailscale",
"runMode": "respawn",
"version": "1.102.4",
"architecture": "armv7hf"
},
"configuration": {
"settingPage": "index.html",
"reverseProxy": [
{
"apiPath": "api",
"target": "http://localhost:2201/",
"access": "admin"
}
],
"paramConfig": [
{
"name": "CustomServer",
"default": "",
"type": "string"
},
{
"name": "AuthKey",
"default": "",
"type": "string"
},
{
"name": "AcceptDNS",
"default": "false",
"type": "string"
},
{
"name": "AcceptRoutes",
"default": "false",
"type": "string"
},
{
"name": "AdvertiseRoutes",
"default": "",
"type": "string"
}
]
}
}
}
+2 -2
View File
@@ -2,8 +2,8 @@ PACKAGENAME=Tailscale_VPN
MENUNAME="Tailscale VPN"
VENDOR="Mo3he"
APPMAJORVERSION=1
APPMINORVERSION=98
APPMICROVERSION=9
APPMINORVERSION=102
APPMICROVERSION=4
APPTYPE=armv7hf
APPNAME=Tailscale_VPN
APPOPTS=""
Executable
+147
View File
@@ -0,0 +1,147 @@
#!/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"
rm -rf "${REPO_ROOT}/debug"
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
# Tailscale is the only upstream here that ships binaries with symbols, so the
# strip is worth ~23 MB per package. It runs inside the SDK container: relying
# on host cross-binutils meant a machine without them silently produced an
# unstripped package under the same version number.
SDK_IMAGE=axisecp/acap-native-sdk:12.10.0
SDK_UBUNTU=ubuntu24.04
strip_arch() {
sdk_arch=$1
suffix=$2
echo "==> Stripping ${suffix} binaries"
# Unstripped copies for symbolising crash dumps; never shipped.
mkdir -p "${REPO_ROOT}/debug"
cp "${BINS}/tailscale_${suffix}" "${REPO_ROOT}/debug/tailscale-${suffix}.unstripped"
cp "${BINS}/tailscaled_${suffix}" "${REPO_ROOT}/debug/tailscaled-${suffix}.unstripped"
# SC2016: $STRIP must expand inside the container, not on the host.
# shellcheck disable=SC2016
cid=$("$RUNTIME" create "${SDK_IMAGE}-${sdk_arch}-${SDK_UBUNTU}" sh -c \
'. /opt/axis/acapsdk/environment-setup* >/dev/null 2>&1 && "${STRIP:?SDK environment did not set STRIP}" /tmp/tailscale /tmp/tailscaled')
"$RUNTIME" cp "${BINS}/tailscale_${suffix}" "${cid}:/tmp/tailscale"
"$RUNTIME" cp "${BINS}/tailscaled_${suffix}" "${cid}:/tmp/tailscaled"
"$RUNTIME" start -a "$cid"
"$RUNTIME" cp "${cid}:/tmp/tailscale" "${BINS}/tailscale_${suffix}"
"$RUNTIME" cp "${cid}:/tmp/tailscaled" "${BINS}/tailscaled_${suffix}"
"$RUNTIME" rm "$cid" >/dev/null
}
strip_arch aarch64 arm64
strip_arch armv7hf 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
+113
View File
@@ -0,0 +1,113 @@
#!/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")
gomodule=$(cfg ".pins[$i].goModule // empty")
[ -f "$file" ] || {
echo "pin target missing: $file" >&2
continue
}
# A go.mod pin has no ARG to substitute, and go refuses a bare "0.77.1", so
# the declared prefix has to be applied here.
if [ -n "$gomodule" ]; then
(cd "$(dirname "$file")" && go get "${gomodule}@${prefix}${UPSTREAM:-$VERSION}" && go mod tidy)
echo "go module ${gomodule}@${prefix}${UPSTREAM:-$VERSION} -> $file"
continue
fi
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
# Web UIs compare the installed version against the latest GitHub release. The
# literal is marked so it cannot drift out of sync with the manifest.
while IFS= read -r page; do
[ -n "$page" ] || continue
sed -i.bak -E "s|'[0-9]+\.[0-9]+\.[0-9]+'( /\* acap:installed-version \*/)|'${VERSION}'\1|g" "$page"
rm -f "$page.bak"
echo "installed-version $VERSION -> $page"
done < <(grep -rl 'acap:installed-version' --include='*.html' . 2>/dev/null || true)
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
+43
View File
@@ -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 straight into
# releases/ depending on the repo, so gather any strays and then count what
# actually ended up in releases/.
while IFS= read -r package; do
[ -n "$package" ] || continue
mv "$package" releases/
done < <(find . -name '*.eap' -not -path './releases/*' -not -path './.git/*')
found=$(find releases -name '*.eap' | wc -l | tr -d ' ')
[ "$found" -gt 0 ] || {
echo "no .eap produced" >&2
exit 1
}
echo "collected $found package(s):"
ls -lh releases/
+67
View File
@@ -0,0 +1,67 @@
#!/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
printf '### Packages\n\n'
printf 'Install the `signed_*.eap` matching your device architecture.\n\n'
# Only explain the unsigned variants when this release actually ships them.
unsigned_note=''
if compgen -G 'releases/*_acap3.eap' >/dev/null 2>&1; then
unsigned_note='`_acap3`'
fi
if compgen -G 'releases/*_root.eap' >/dev/null 2>&1; then
[ -n "$unsigned_note" ] && unsigned_note="${unsigned_note} and "
unsigned_note="${unsigned_note}\`_root\`"
fi
if [ -n "$unsigned_note" ]; then
printf 'Packages ending %s are published unsigned by design:\n' "$unsigned_note"
printf 'they use manifest schema 1.x, which the Axis signing service does not accept.\n\n'
fi
if [ -n "$PREVIOUS" ] && [ -n "${GITHUB_REPOSITORY:-}" ]; then
printf '**Full changelog**: https://github.com/%s/compare/%s...v%s\n' \
"$GITHUB_REPOSITORY" "$PREVIOUS" "$VERSION"
fi
+171
View File
@@ -0,0 +1,171 @@
#!/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))"
}
# True when $1 is a strictly higher version than $2.
version_gt() {
[ "$1" != "$2" ] && [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | tail -1)" = "$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
if version_gt "$UPSTREAM" "$CURRENT"; then
# Upstream is ahead: adopt its version.
TARGET="$UPSTREAM"
BUILD=true
RELEASE=true
elif [ "$UPSTREAM" != "$(pin_value)" ]; then
# Our line already ran past upstream, so keep moving forward on it
# rather than emitting a lower version that clashes with old tags.
TARGET=$(bump_patch "$CURRENT")
BUILD=true
RELEASE=true
fi
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
# Never aim at a version that is already published. Stepping forward here means
# a long build is not wasted only to be rejected by the release job. An explicit
# version input is respected as given.
if [ "$RELEASE" = true ] && [ -z "$INPUT_VERSION" ] && command -v gh >/dev/null 2>&1; then
attempts=0
while [ "$attempts" -lt 20 ] &&
gh release view "v$TARGET" --json isDraft --jq '.isDraft' 2>/dev/null | grep -qx false; do
echo "v$TARGET is already published; stepping forward"
TARGET=$(bump_patch "$TARGET")
attempts=$((attempts + 1))
done
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
+21
View File
@@ -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