Compare commits

...
22 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
Weston Blieden 92262bd16c Update vendor to moshe@mohome.net and vendorId for ACAP signing
Packages are now signed with the Axis ACAP signing service. Document the
signing change and the upgrade steps (back up config and uninstall the old
version to avoid the "Vendor ID in manifest does not match" install error)
in the README and changelog.
2026-07-21 19:35:05 +02:00
18 changed files with 1011 additions and 425 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
+15
View File
@@ -5,6 +5,21 @@ 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
normally on AXIS OS 12.10 and later.
- Vendor updated to `moshe@mohome.net` with the registered vendor ID.
- `root` and `acap3` variants remain unsigned (manifest schema v1.x).
- Upgrading from an earlier unsigned version can fail with "Couldn't
install: app" (device log: "Vendor ID in manifest does not match the
vendor ID of the previous version"). Back up your config, uninstall the
old version, then install this one.
## [1.98.8-2] - 2026-07-03 - Tailscale VPN 1.98.8-2
## [1.98.8-subnet-routing] - 2026-07-01 - Tailscale VPN 1.98.8 - Subnet Routing
+17 -4
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,8 +71,19 @@ 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
> signing service and install normally on AXIS OS 12.10 and later.
>
> **Upgrading from an earlier version?** The signing vendor changed, so
> installing over a previously installed unsigned build can fail with
> **"Couldn't install: app"** (device log: *"Vendor ID in manifest does not
> match the vendor ID of the previous version"*). To upgrade: back up your app
> configuration, **uninstall** the old version, then install the signed one.
Get the **prebuilt `.eap` file** from the
[Releases page](https://github.com/Mo3he/Axis_Cam_Tailscale/releases).
@@ -107,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
@@ -209,14 +221,15 @@ 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
relative-path requests, so it inherits the page's protocol with no
mixed-content risk.
- [ ] **Sign the ACAP via the Axis ACAP Portal** - Deferred; the manifest's
`vendorId` is a placeholder value, not yet portal-registered.
- [x] **Sign the ACAP via the Axis ACAP Portal** - Done; `aarch64`/`armv7hf`
packages are signed with the Axis ACAP signing service. The `root` and
`acap3` variants use manifest schema v1.x and are distributed unsigned.
### General Improvements
+63 -63
View File
@@ -1,67 +1,67 @@
{
"schemaVersion": "2.0.0",
"acapPackageConf": {
"setup": {
"appName": "Tailscale_VPN",
"friendlyName": "Tailscale VPN",
"vendor": "Mo3he",
"vendorId": "5741c1fb91",
"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": "Mo3he",
"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": "Mo3he",
"vendorId": "5741c1fb91",
"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": "Mo3he",
"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