Repository navigation
178 lines (171 loc) · 9.99 KB
/
Copy pathrelease.yml
File metadata and controls
178 lines (171 loc) · 9.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# One release per version tag: ZIP (funscript-player-vX.Y.Z.zip, up to 1.x handy-player-linux-…) + text from CHANGELOG.md (English, state of main)
# + Docker image on GitHub (ghcr.io/scripthost/funscript-player:X.Y.Z and :latest, amd64 + arm64).
# Pre-release (tag with „-“, e.g. v2.0.0-beta1): „Pre-release“, image with the version number only, :latest stays.
# New tag -> only this one.
# Manuell (Actions -> Release -> Run workflow):
# recreate = false -> create releases for all tags that have none
# recreate = true -> delete ALL releases and create them again (English text, ZIP from the respective tag)
# Same on a tag: if the tag message contains „[alle Releases neu]“, all releases are recreated.
name: Release
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
recreate:
description: 'Alle Releases löschen und neu erzeugen'
type: boolean
default: false
# Read-only by default – only the job that needs write access gets it
permissions:
contents: read
jobs:
# Test first – release and image only when all tests are green
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: 22
- run: node --test "test/*.test.js" # inkl. Browser-Selbsttest in Chrome (test/browser.test.js)
# Code check (1.19.9): only here, not in the image
- run: npm ci --no-audit --no-fund && npx eslint . # versions pinned in package-lock.json
- run: npm run typecheck # test14: type check of server + core.mjs (development only, not in the image)
# Start the container the way docker-compose.yml does (read-only, without Linux capabilities)
# and check that the self-test and the interface run
container:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Build und Start
run: |
docker build -q -t fsp-test .
docker run -d --name fsp --read-only --tmpfs /tmp --cap-drop ALL \
--security-opt no-new-privileges:true --memory 2g -e LOGIN=off -v fsp-data:/app/data -p 8080:8080 fsp-test
for i in $(seq 1 60); do docker logs fsp 2>&1 | grep -q "✓ ready" && break; sleep 1; done
docker logs fsp 2>&1
docker logs fsp 2>&1 | grep -q "✓ ready"
r=$(docker logs fsp 2>&1 | grep -oE "Self-test: [0-9]+/[0-9]+" | cut -d' ' -f2)
[ -n "$r" ] && [ "${r%/*}" = "${r#*/}" ]
curl -fsS http://localhost:8080/healthz
curl -fsS http://localhost:8080/ | grep -q "main.js"
release:
needs: [test, container]
runs-on: ubuntu-latest
permissions:
contents: write # Release anlegen
id-token: write # Signature without an own key (Sigstore)
steps:
- uses: sigstore/cosign-installer@756fe2d284c7292c4e9b62c8a28f1e5629c59532 # v4.1.3
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
- name: Create releases
env:
GH_TOKEN: ${{ github.token }}
RECREATE: ${{ inputs.recreate }}
run: |
git fetch --force --tags -q # Full tag messages (checkout makes the tag „lightweight“ otherwise)
git show origin/main:CHANGELOG.md > changelog.en.md
# Pre-releases (tag with „-“, e.g. v2.0.0-beta1): release as „Pre-release“, never „Latest“, image without :latest.
# versionsort.suffix: v2.0.0-beta1 sortiert VOR v2.0.0
vsort() { git -c versionsort.suffix=- tag --sort=v:refname; }
newest=$(vsort | grep -v -- - | tail -1)
if [ "$GITHUB_REF_TYPE" = "tag" ]; then tags="$GITHUB_REF_NAME"; else tags=$(vsort); fi
if [ "$GITHUB_REF_TYPE" = "tag" ] && git tag -l --format="%(contents)" "$GITHUB_REF_NAME" | grep -qF "[alle Releases neu]"; then
RECREATE=true; tags=$(vsort)
fi
# Report an error of one version as a notice (publicly readable, also without login) and carry on
fail=0
err() { echo "::error title=$1::$(tr '\n' ' ' < err.txt | cut -c1-400)"; fail=1; }
# GitHub sometimes answers with 502 – up to 4 attempts
retry() { for i in 1 2 3 4; do "$@" 2>err.txt && return 0; sleep $((i * 10)); done; return 1; }
for t in $tags; do
if gh release view "$t" >/dev/null 2>&1; then
if [ "$RECREATE" != "true" ]; then echo "$t: Release gibt es schon"; continue; fi
# already in the new format (English, with image note)? then it stays – a draft (created when
# a tag is set again or GitHub hangs while creating it) is published
if gh release view "$t" --json body -q .body | grep -qF "ghcr.io/scripthost/funscript-player"; then
if [ "$(gh release view "$t" --json isDraft -q .isDraft)" = "true" ]; then
if [ "$t" = "$newest" ]; then latest="--latest"; else latest="--latest=false"; fi
retry gh release edit "$t" --draft=false $latest || err "$t veröffentlichen"
echo "$t: Entwurf veröffentlicht"
else echo "$t: schon neu"; fi
continue
fi
retry gh release delete "$t" --yes || { err "$t löschen"; continue; }
fi
v="${t#v}"
# ZIP name: from 2.0 funscript-player-vX.Y.Z.zip (folder funscript-player), 1.x keeps the old name
case "$t" in
v1.*) zip="handy-player-linux-$t.zip"; dir="handy-player" ;;
*) zip="funscript-player-$t.zip"; dir="funscript-player" ;;
esac
# Fixed block „Install / update“ on top, below it the text from CHANGELOG.md
pre=""; upd='`docker compose pull && docker compose up -d`'
case "$v" in *-*)
pre="--prerelease"
upd="set \`PLAYER_VERSION=$v\` in \`.env\`, then \`docker compose pull && docker compose up -d\` (back to stable: \`PLAYER_VERSION=latest\`)" ;;
esac
{
[ -n "$pre" ] && printf '> **Pre-release** – `:latest` stays on the last stable version.\n\n'
printf '**Install:** download `%s` below → `unzip %s && cd %s` → `cp .env.example .env` → edit `.env` → `docker compose up -d` ([README](https://github.com/ScriptHost/funscript-player#-quick-start)) \n' "$zip" "$zip" "$dir"
printf '**Update:** %s – your `.env` and data stay.\n\n---\n\n' "$upd"
awk -v v="$v" 'index($0, "## " v " ") == 1 || $0 == "## " v { f = 1; next } /^## / && f { exit } f' changelog.en.md > body.md
if [ -s body.md ]; then cat body.md; else echo "Version $v"; fi
printf '\n---\nDocker image `ghcr.io/scripthost/funscript-player:%s` (amd64 + arm64)\n' "$v"
} > notes.md
git archive --format=zip --prefix="$dir/" -o "$zip" "$t" -- . ':!.gitignore' ':!windows' ':!.github' 2>err.txt || { err "$t ZIP"; continue; }
# Checksum to verify: sha256sum -c funscript-player-vX.Y.Z.zip.sha256
sha256sum "$zip" > "$zip.sha256"
# Signatur (Sigstore, an diesen Workflow gebunden): cosign verify-blob --bundle <zip>.sigstore.json …
# If signing fails, the release still comes (with a warning) – never a missing release because of it
sig="$zip.sigstore.json"
cosign sign-blob --yes --bundle "$sig" "$zip" >/dev/null 2>err.txt || { echo "::warning title=$t Signatur::$(tr '\n' ' ' < err.txt | cut -c1-300)"; sig=""; }
printf '\nSHA-256 of the ZIP: `%s` – the image comes with an SBOM and a build provenance attestation.\n' "$(cut -d' ' -f1 "$zip.sha256")" >> notes.md
if [ "$t" = "$newest" ]; then latest="--latest"; else latest="--latest=false"; fi
retry gh release create "$t" "$zip" "$zip.sha256" $sig --title "$v" --notes-file notes.md $latest $pre || { err "$t anlegen"; continue; }
echo "$t: Release erstellt"
done
exit $fail
image:
# Docker image only for a new tag (or the newest one on a manual run)
needs: [test, container]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # Image hochladen
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
- name: Version
id: v
run: |
t="${GITHUB_REF_NAME}"
[ "$GITHUB_REF_TYPE" = "tag" ] || t=$(git -c versionsort.suffix=- tag --sort=v:refname | tail -1)
git checkout -q "$t"
echo "version=${t#v}" >> "$GITHUB_OUTPUT"
# Pre-release (e.g. 2.0.0-beta1): only the version number, :latest stays on the last stable one
case "$t" in *-*) echo "latest=false" >> "$GITHUB_OUTPUT" ;; *) echo "latest=true" >> "$GITHUB_OUTPUT" ;; esac
# ARM64 (2.0): Apple silicon, Raspberry Pi … – built via QEMU on the amd64 runner
- uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0
# SBOM and provenance only work with the buildx container driver (not with the default „docker“ driver)
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4
- uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
# Software bill of materials (SBOM) and provenance (where and from what it was built) are attached to the image
sbom: true
provenance: mode=max
tags: |
ghcr.io/scripthost/funscript-player:${{ steps.v.outputs.version }}
${{ steps.v.outputs.latest == 'true' && 'ghcr.io/scripthost/funscript-player:latest' || '' }}