The fork takes its own name everywhere a user can see it: binaries (voculinux, voculinux-gui), desktop entry, autostart, tray, D-Bus namespace (com.voculinux.Voculinux, no alias for the old name), config/data/cache dirs, single-instance lock, IBus engine identity, tray icons, resource prefixes, VOCULINUX_* environment names, and the update-checker user agent. The internal Python module stays vocalinux and the distribution metadata keeps the upstream URLs as provenance. On first run a previous Vocalinux install's config.json, custom dictionary, and portal restore token are copied once into the new locations (new config always wins, no merge, no write-back); models re-resolve through the pinned-checksum download path. An offline naming-sweep test proves no old-name strings remain on user-visible surfaces outside the allowlist. Refs #18
128 lines
5.0 KiB
YAML
128 lines
5.0 KiB
YAML
name: Promote snap to stable
|
|
|
|
# Issue #783, the "safer" option: `v*` tags keep auto-publishing the snap to
|
|
# edge + candidate in release.yml; latest/stable stays gated on candidate QA.
|
|
# After QA (install, tray, mic, model download, typing into a real app), run:
|
|
# gh workflow run snap-promote.yml -f tag=v0.18.0
|
|
#
|
|
# The candidate revision must carry the tag's version, so a typo'd tag or a
|
|
# stale candidate fails the run instead of promoting the wrong revision.
|
|
# The `snap-stable` environment is an optional second gate: add required
|
|
# reviewers to it in repo settings and each dispatch waits for an approval.
|
|
# Caller-supplied tag values stay in the environment, never in run: source.
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: "Release tag whose candidate snap to promote (e.g. v0.18.0)"
|
|
required: true
|
|
type: string
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
# Promotions serialize: a second dispatch queues instead of racing the first.
|
|
concurrency:
|
|
group: snap-promote-stable
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
promote:
|
|
name: Promote latest/candidate to latest/stable
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
environment:
|
|
name: snap-stable
|
|
steps:
|
|
- name: Require a version tag
|
|
env:
|
|
TAG: ${{ inputs.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
case "$TAG" in
|
|
v[0-9]*) ;;
|
|
*)
|
|
echo "refusing tag that is not v<digit>…: $TAG" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
if printf '%s' "$TAG" | grep -q '[^A-Za-z0-9._-]'; then
|
|
echo "refusing tag with unexpected characters: $TAG" >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Require Snap Store credentials
|
|
env:
|
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
|
|
run: |
|
|
if [ -z "$SNAPCRAFT_STORE_CREDENTIALS" ]; then
|
|
echo "SNAPCRAFT_STORE_CREDENTIALS is unset; cannot promote the snap"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Install snapcraft
|
|
run: sudo snap install snapcraft --classic
|
|
|
|
# `snapcraft status` prints the channel map grouped by track and arch.
|
|
# The candidate row is normally a continuation line (column 1 is the
|
|
# channel), but if candidate ever leads the latest track block it rides
|
|
# the track line itself — column 3 is the channel then — so match both.
|
|
- name: Resolve the candidate revision
|
|
id: candidate
|
|
env:
|
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
|
|
TAG: ${{ inputs.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
VERSION="${TAG#v}"
|
|
/snap/bin/snapcraft status voculinux | tee /tmp/snap-status.txt
|
|
# POSIX: mawk has no \S — use a literal non-space class.
|
|
row=$(awk '/^[^ \t]/ { track = $1 }
|
|
track == "latest" && $1 == "candidate" { print $2, $3; exit }
|
|
track == "latest" && $3 == "candidate" { print $4, $5; exit }' \
|
|
/tmp/snap-status.txt)
|
|
read -r seen_version revision <<<"$row"
|
|
echo "latest/candidate holds version=${seen_version:-none} revision=${revision:-none}"
|
|
case "${revision:-}" in
|
|
''|*[!0-9]*)
|
|
echo "No promotable revision on latest/candidate (stale or under review?)" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
if [ "$seen_version" != "$VERSION" ]; then
|
|
echo "candidate version '$seen_version' does not match tag version '$VERSION'" >&2
|
|
exit 1
|
|
fi
|
|
echo "revision=$revision" >> "$GITHUB_OUTPUT"
|
|
|
|
# Re-resolve right before releasing: if a newer release published a
|
|
# different candidate after the first check, stable would downgrade.
|
|
- name: Re-verify the candidate did not move
|
|
env:
|
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
|
|
TAG: ${{ inputs.tag }}
|
|
REVISION: ${{ steps.candidate.outputs.revision }}
|
|
run: |
|
|
set -euo pipefail
|
|
VERSION="${TAG#v}"
|
|
row=$(awk '/^[^ \t]/ { track = $1 }
|
|
track == "latest" && $1 == "candidate" { print $2, $3; exit }
|
|
track == "latest" && $3 == "candidate" { print $4, $5; exit }' \
|
|
<(/snap/bin/snapcraft status voculinux))
|
|
read -r seen_version revision <<<"$row"
|
|
if [ "${revision:-}" != "$REVISION" ] || [ "$seen_version" != "$VERSION" ]; then
|
|
echo "latest/candidate moved on (now version=${seen_version:-none} revision=${revision:-none}); aborting instead of promoting the stale revision" >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Release the resolved revision to latest/stable
|
|
env:
|
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
|
|
REVISION: ${{ steps.candidate.outputs.revision }}
|
|
run: |
|
|
set -euo pipefail
|
|
/snap/bin/snapcraft release voculinux "$REVISION" latest/stable
|
|
/snap/bin/snapcraft status voculinux
|