mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 22:03:13 -06:00
Four loose files that must all land intact, in the right place, is a poor way to
hand someone a player. autorun.zip is one file: drop it on the root of a
player's storage, power-cycle, and autozip.brs unpacks it in place and reboots
into the player. A half-copied set of loose files boots into something broken; a
half-copied zip simply fails to extract and leaves the player as it was.
Two rules the format imposes, both of which fail SILENTLY when broken, so the
build script asserts them instead of trusting them:
- the archive must expand to files at its root, with no wrapper directory. A
player extracts to the storage root, so a nested folder puts autorun.brs
somewhere the player never looks and the card appears to do nothing.
- autorun.brs must not sit next to autorun.zip on the storage root; its
presence stops the zip being processed at all.
autozip.brs renames the archive to autorun.zip.done after a successful extract,
which is what makes it idempotent — without that the player extracts, reboots,
extracts, reboots, a loop indistinguishable from a hardware fault. A FAILED
extract deliberately does not rename, so a truncated copy gets retried once
someone replaces it rather than being skipped forever.
It is volume-aware for the same reason autorun.brs is: a player may be booting
from internal flash because its card interface is dead, and extracting to "SD:/"
on such a unit writes to a volume that does not exist.
--server rewrites screentinker.json in the staging copy so a batch can be imaged
for a specific instance without hand-editing anything.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL
184 lines
7.6 KiB
YAML
184 lines
7.6 KiB
YAML
name: Release
|
|
|
|
# Fires when a version tag is pushed (e.g. v1.8.0). Builds + publishes artifacts
|
|
# only - nothing here deploys to production.
|
|
on:
|
|
push:
|
|
tags: ['v*']
|
|
|
|
permissions:
|
|
contents: write # create the GitHub Release
|
|
packages: write # push the image to ghcr.io
|
|
|
|
concurrency:
|
|
group: release-${{ github.ref }}
|
|
cancel-in-progress: false # never cancel a release mid-publish
|
|
|
|
jobs:
|
|
# Fail-fast: a hand-pushed tag that disagrees with VERSION must not publish
|
|
# anything (the artifacts would report the wrong version). Gates everything.
|
|
verify:
|
|
name: Verify tag matches VERSION
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- name: Assert pushed tag equals VERSION
|
|
run: |
|
|
TAG="${GITHUB_REF_NAME#v}"
|
|
FILE="$(cat VERSION)"
|
|
echo "pushed tag: ${GITHUB_REF_NAME} (stripped: $TAG) VERSION file: $FILE"
|
|
if [ "$TAG" != "$FILE" ]; then
|
|
echo "::error::Tag ${GITHUB_REF_NAME} does not match VERSION ($FILE) - refusing to publish."
|
|
exit 1
|
|
fi
|
|
echo "OK: tag matches VERSION ($FILE)"
|
|
|
|
test:
|
|
name: Tests
|
|
needs: verify
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: server
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: '20'
|
|
cache: npm
|
|
cache-dependency-path: server/package-lock.json
|
|
- run: npm ci
|
|
- run: npm test
|
|
|
|
artifacts:
|
|
name: Tarball + Tizen .wgt + GitHub Release
|
|
needs: test
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0 # full history, for release notes
|
|
|
|
- name: Resolve version + previous tag
|
|
id: ver
|
|
run: |
|
|
VERSION="$(cat VERSION)"
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
|
|
PREV="$(git describe --tags --abbrev=0 "${GITHUB_REF_NAME}^" 2>/dev/null || true)"
|
|
echo "prev=$PREV" >> "$GITHUB_OUTPUT"
|
|
# #80: a version carrying a -suffix (e.g. 1.9.0-rc1) is a pre-release.
|
|
case "$VERSION" in *-*) PRE=true ;; *) PRE=false ;; esac
|
|
echo "prerelease=$PRE" >> "$GITHUB_OUTPUT"
|
|
echo "Releasing ${GITHUB_REF_NAME} (version $VERSION, prerelease=$PRE); previous tag: ${PREV:-<none>}"
|
|
|
|
- name: Build Tizen .wgt (unsigned in CI)
|
|
run: |
|
|
chmod +x tizen/build-wgt.sh
|
|
( cd tizen && ./build-wgt.sh ) # no Tizen CLI on the runner => unsigned zip
|
|
cp tizen/ScreenTinker.wgt ScreenTinker.wgt
|
|
ls -la ScreenTinker.wgt
|
|
|
|
- name: Build BrightSign autorun.zip (single-file player installer)
|
|
run: |
|
|
chmod +x scripts/build-autorun-zip.sh
|
|
./scripts/build-autorun-zip.sh -o autorun.zip
|
|
ls -la autorun.zip
|
|
|
|
- name: Build source tarball (bundles the .wgt; the signed apk is added by scripts/finalize-release.sh)
|
|
run: |
|
|
OUT="screentinker-${{ steps.ver.outputs.version }}.tar.gz"
|
|
tar czf "$OUT" \
|
|
--exclude='node_modules' --exclude='.git' --exclude='.github' \
|
|
--exclude='*.db' --exclude='*.db-wal' --exclude='*.db-shm' --exclude='*.db.*' \
|
|
--exclude='server/uploads' --exclude='server/certs' --exclude='server/test' \
|
|
--exclude='*.apk' \
|
|
server frontend scripts docs VERSION README.md LICENSE .env.example ScreenTinker.wgt brightsign
|
|
echo "TARBALL=$OUT" >> "$GITHUB_ENV"
|
|
ls -la "$OUT"
|
|
|
|
- name: Generate release notes
|
|
run: |
|
|
PREV="${{ steps.ver.outputs.prev }}"
|
|
{
|
|
echo "## ScreenTinker ${{ steps.ver.outputs.tag }}"
|
|
echo
|
|
echo "### Changes"
|
|
if [ -n "$PREV" ]; then
|
|
git log --no-merges --pretty='- %s' "${PREV}..${{ steps.ver.outputs.tag }}"
|
|
else
|
|
echo "_First tagged release. Most recent changes:_"
|
|
git log --no-merges --pretty='- %s' -n 30 "${{ steps.ver.outputs.tag }}"
|
|
fi
|
|
echo
|
|
echo "### Artifacts"
|
|
echo "- \`${TARBALL}\` - bundle: server + frontend source + the Tizen .wgt (the signed Android APK is added at the root during release finalization)."
|
|
echo "- \`ScreenTinker.wgt\` - Tizen TV web app, **unsigned - for inspection only**."
|
|
echo " Sign it with your own Samsung certificate (Tizen Studio + a profile that includes"
|
|
echo " your TV's DUID) to install, or - easiest - point a Tizen TV browser / URL Launcher"
|
|
echo " at \`https://<your-instance>/player\` (no signing needed)."
|
|
echo "- \`autorun.zip\` - BrightSign player installer. Drop it on the root of a player's"
|
|
echo " storage (microSD, USB, or internal flash) and power-cycle: it unpacks itself and"
|
|
echo " reboots into the player. Edit \`screentinker.json\` inside the archive first to"
|
|
echo " point it at your own server."
|
|
if [ "${{ steps.ver.outputs.prerelease }}" = "true" ]; then
|
|
echo "- Docker image: \`ghcr.io/screentinker/screentinker:${{ steps.ver.outputs.version }}\` (pre-release - \`:latest\` is NOT moved)."
|
|
else
|
|
echo "- Docker image: \`ghcr.io/screentinker/screentinker:${{ steps.ver.outputs.version }}\` (also \`:latest\`)."
|
|
fi
|
|
echo "- \`ScreenTinker.apk\` - signed Android player (attached during release finalization)."
|
|
} > RELEASE_NOTES.md
|
|
cat RELEASE_NOTES.md
|
|
|
|
- name: Create GitHub Release
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
# #80: pre-release tags publish as a GitHub *pre-release* (not "Latest"),
|
|
# which also keeps the /releases/latest API pointing at the last stable.
|
|
PRERELEASE_FLAG=""
|
|
[ "${{ steps.ver.outputs.prerelease }}" = "true" ] && PRERELEASE_FLAG="--prerelease"
|
|
gh release create "${{ steps.ver.outputs.tag }}" \
|
|
$PRERELEASE_FLAG \
|
|
--title "ScreenTinker ${{ steps.ver.outputs.tag }}" \
|
|
--notes-file RELEASE_NOTES.md \
|
|
"${TARBALL}" \
|
|
tizen/ScreenTinker.wgt
|
|
|
|
docker:
|
|
name: Docker image (amd64 + arm64) -> ghcr
|
|
needs: test
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- id: ver
|
|
run: |
|
|
VERSION="$(cat VERSION)"
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
# #80: move :latest only for final releases - a pre-release (1.9.0-rc1) must
|
|
# not repoint :latest onto untested code (anyone on :latest pulls it on restart).
|
|
TAGS="ghcr.io/screentinker/screentinker:$VERSION"
|
|
case "$VERSION" in
|
|
*-*) echo "Pre-release $VERSION: :latest will NOT be moved" ;;
|
|
*) TAGS="${TAGS}"$'\n'"ghcr.io/screentinker/screentinker:latest" ;;
|
|
esac
|
|
{ echo "tags<<__EOF__"; printf '%s\n' "$TAGS"; echo "__EOF__"; } >> "$GITHUB_OUTPUT"
|
|
- uses: docker/setup-qemu-action@v3
|
|
- uses: docker/setup-buildx-action@v3
|
|
- uses: docker/login-action@v3
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
- uses: docker/build-push-action@v6
|
|
with:
|
|
context: .
|
|
platforms: linux/amd64,linux/arm64
|
|
push: true
|
|
tags: ${{ steps.ver.outputs.tags }}
|
|
|
|
# TODO (deferred): build + sign the Android APK in CI. Requires the release
|
|
# keystore + passwords as encrypted Actions secrets. For now the maintainer
|
|
# attaches a signed APK out-of-band (and self-hosters mount one at
|
|
# /data/ScreenTinker.apk).
|