-
Notifications
You must be signed in to change notification settings - Fork 0
393 lines (346 loc) · 15.8 KB
/
Copy pathrelease.yml
File metadata and controls
393 lines (346 loc) · 15.8 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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
name: Release
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+-*'
# Each build job is independent so all three platforms compile in parallel.
# The final job collects all artifacts and creates a draft GitHub Release.
jobs:
# ---------------------------------------------------------------------------
# macOS — .app bundle + .pkg installer.
#
# The job runs in three modes depending on which repository secrets are set:
#
# 1. No signing secrets:
# Produces UNSIGNED .app + UNSIGNED .pkg (current baseline behavior).
#
# 2. Code-signing secrets set (APPLE_DEVELOPER_ID_APPLICATION,
# APPLE_CERTIFICATES_P12_BASE64, APPLE_CERTIFICATES_P12_PASSWORD):
# Signs the .app with Hardened Runtime + entitlements; signs the .pkg
# if APPLE_DEVELOPER_ID_INSTALLER is also set.
#
# 3. Above PLUS notarization secrets set (APPLE_ID,
# APPLE_APP_SPECIFIC_PASSWORD, APPLE_TEAM_ID):
# Submits to Apple notary service, waits, and staples.
#
# Required repository secrets for full signed+notarized distribution:
#
# APPLE_DEVELOPER_ID_APPLICATION Certificate common name, e.g.
# "Developer ID Application: NAME (TEAMID)"
# APPLE_DEVELOPER_ID_INSTALLER Installer cert name, e.g.
# "Developer ID Installer: NAME (TEAMID)"
# APPLE_CERTIFICATES_P12_BASE64 Base64-encoded P12 containing both certs
# and their private keys
# APPLE_CERTIFICATES_P12_PASSWORD Password protecting the P12
# APPLE_ID Apple ID email for notarytool
# APPLE_APP_SPECIFIC_PASSWORD App-specific password (appleid.apple.com)
# APPLE_TEAM_ID 10-character Apple Developer Team ID
#
# See macos/PACKAGING.md for how-to-obtain instructions.
# ---------------------------------------------------------------------------
build-macos:
name: Build macOS
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- name: Select Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Detect signing secrets
id: signing
env:
P12_B64: ${{ secrets.APPLE_CERTIFICATES_P12_BASE64 }}
run: |
if [[ -n "${P12_B64:-}" ]]; then
echo "have_certs=true" >> "$GITHUB_OUTPUT"
else
echo "have_certs=false" >> "$GITHUB_OUTPUT"
fi
- name: Import code-signing certificates
if: steps.signing.outputs.have_certs == 'true'
uses: apple-actions/import-codesign-certs@v3
with:
p12-file-base64: ${{ secrets.APPLE_CERTIFICATES_P12_BASE64 }}
p12-password: ${{ secrets.APPLE_CERTIFICATES_P12_PASSWORD }}
- name: Build .app bundle (sign + notarize when secrets present)
env:
APPLE_DEVELOPER_ID_APPLICATION: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: bash macos/scripts/build-app.sh
- name: Build .pkg installer (signed when installer cert present)
env:
APPLE_DEVELOPER_ID_INSTALLER: ${{ secrets.APPLE_DEVELOPER_ID_INSTALLER }}
run: bash macos/scripts/build-pkg.sh
- name: Zip .app bundle
run: |
cd macos/build
zip -r --symlinks "InterlinedSync-macOS-${{ github.ref_name }}.zip" InterlinedSync.app
- name: Rename .pkg for release
run: |
cd macos/build
PKG_SRC="$(ls InterlinedSync-*.pkg | grep -v "macOS-${{ github.ref_name }}" | head -n 1 || true)"
if [[ -n "${PKG_SRC}" ]]; then
mv "${PKG_SRC}" "InterlinedSync-macOS-${{ github.ref_name }}.pkg"
fi
- name: Upload macOS artifacts
uses: actions/upload-artifact@v4
with:
name: macos-release
path: |
macos/build/InterlinedSync-macOS-${{ github.ref_name }}.zip
macos/build/InterlinedSync-macOS-${{ github.ref_name }}.pkg
if-no-files-found: error
retention-days: 1
# ---------------------------------------------------------------------------
# Windows — self-contained publish (bundles the .NET 9 runtime) PLUS an
# Inno Setup installer (InterlinedListSync-Setup-<tag>.exe). Both are
# uploaded; the installer is the recommended distribution channel and
# the .zip remains for users who prefer xcopy deploy. The installer is
# signed when WINDOWS_CERT_PFX_BASE64 + WINDOWS_CERT_PASSWORD secrets
# exist; otherwise it ships UNSIGNED (SmartScreen will warn on first
# install). See windows/PACKAGING.md for the full signing checklist.
#
# NOTE: --self-contained true ships the .NET 9 runtime inside the publish
# directory so the app launches on machines that do NOT have the .NET
# Desktop Runtime pre-installed. Earlier framework-dependent builds caused
# a silent no-op on first install for users without that runtime. We
# deliberately leave PublishTrimmed unset (default false) — WPF and the
# reflection-heavy DI graph break under trimming. We also stay folder-
# based (no PublishSingleFile) so the Inno [Files] glob keeps working
# without changes.
# MSIX is deferred — see comment further down.
# ---------------------------------------------------------------------------
build-windows:
name: Build Windows
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET 9
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v2
- name: Restore packages
working-directory: windows
run: dotnet restore
- name: Publish
working-directory: windows
run: >
dotnet publish InterlinedSync/InterlinedSync.csproj
--configuration Release
--no-restore
--runtime win-x64
--self-contained true
--output publish
- name: Zip publish output
shell: pwsh
run: |
Compress-Archive `
-Path windows/publish/* `
-DestinationPath "InterlinedSync-Windows-${{ github.ref_name }}.zip"
- name: Install Inno Setup
shell: pwsh
run: choco install innosetup -y --no-progress
- name: Compute installer version
id: insver
shell: pwsh
run: |
# Strip a leading 'v' to match Inno's VersionInfoVersion expectations
# (Inno tolerates the suffix but the leading 'v' confuses some
# Windows VersionInfo consumers).
$tag = "${{ github.ref_name }}"
$ver = $tag -replace '^v',''
"version=$ver" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
- name: Detect Windows signing secrets
id: winsign
shell: pwsh
env:
PFX_B64: ${{ secrets.WINDOWS_CERT_PFX_BASE64 }}
run: |
if ($env:PFX_B64) {
"have_cert=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
} else {
"have_cert=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
}
- name: Decode signing certificate
if: steps.winsign.outputs.have_cert == 'true'
shell: pwsh
env:
PFX_B64: ${{ secrets.WINDOWS_CERT_PFX_BASE64 }}
run: |
$bytes = [Convert]::FromBase64String($env:PFX_B64)
[IO.File]::WriteAllBytes("$env:RUNNER_TEMP\codesign.pfx", $bytes)
- name: Compile Inno Setup installer (unsigned)
if: steps.winsign.outputs.have_cert != 'true'
shell: pwsh
run: |
& "C:\Program Files (x86)\Inno Setup 6\iscc.exe" `
/Qp `
/DAppVersion=${{ steps.insver.outputs.version }} `
windows/installer/InterlinedSync.iss
- name: Compile Inno Setup installer (signed)
if: steps.winsign.outputs.have_cert == 'true'
shell: pwsh
env:
PFX_PASSWORD: ${{ secrets.WINDOWS_CERT_PASSWORD }}
run: |
# Locate signtool (newest Windows SDK build).
$signtool = Get-ChildItem `
-Path "C:\Program Files (x86)\Windows Kits\10\bin" `
-Recurse -Filter signtool.exe `
| Where-Object { $_.FullName -like "*\x64\signtool.exe" } `
| Sort-Object FullName -Descending `
| Select-Object -First 1
if (-not $signtool) {
throw "signtool.exe not found on runner."
}
$pfx = "$env:RUNNER_TEMP\codesign.pfx"
# Inno invokes the named SignTool template once per file ($f).
$signCmd = "`"$($signtool.FullName)`" sign /fd sha256 /tr http://timestamp.digicert.com /td sha256 /f `"$pfx`" /p `"$env:PFX_PASSWORD`" `$f"
& "C:\Program Files (x86)\Inno Setup 6\iscc.exe" `
/Qp `
/DAppVersion=${{ steps.insver.outputs.version }} `
/DSignToolConfigured=1 `
"/Ssigntool=$signCmd" `
windows/installer/InterlinedSync.iss
- name: Stage installer for upload
shell: pwsh
run: |
# The .iss outputs to windows/installer/Output/. Copy to a flat
# location so the upload-artifact glob below picks it up reliably.
Copy-Item `
-Path "windows/installer/Output/InterlinedListSync-Setup-${{ steps.insver.outputs.version }}.exe" `
-Destination "InterlinedListSync-Setup-${{ github.ref_name }}.exe"
# -------------------------------------------------------------------
# MSIX packaging deferred from CI: the windows-latest runner's
# Windows SDK does not include UAP.props for our target version
# (APPX3217 across multiple version values). Build MSIX on a
# maintainer machine via Visual Studio for now; revisit once we
# standardize on a runner with the right SDK pre-installed.
# See windows/PACKAGING.md.
# -------------------------------------------------------------------
- name: Upload Windows artifacts
uses: actions/upload-artifact@v4
with:
name: windows-release
path: |
InterlinedSync-Windows-${{ github.ref_name }}.zip
InterlinedListSync-Setup-${{ github.ref_name }}.exe
if-no-files-found: error
retention-days: 1
# ---------------------------------------------------------------------------
# Linux — .deb package (via cargo-deb) + standalone binary tarball + .snap
# Snap build uses snapcraft with --destructive-mode (no LXD in GitHub runners).
# Publishing to the Snap Store is a separate manual step — see PACKAGING.md.
# ---------------------------------------------------------------------------
build-linux:
name: Build Linux
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
pkg-config \
libdbus-1-dev \
libsqlite3-dev \
libsecret-1-dev
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: linux-ubuntu
- name: Build release binary
working-directory: linux-ubuntu
run: cargo build --release
- name: Install cargo-deb
run: cargo install cargo-deb --version "2.4.0"
- name: Build .deb package
working-directory: linux-ubuntu
run: cargo deb -p interlinedlist-sync --no-build
- name: Create binary tarball
run: |
tar -czf "linux-ubuntu/InterlinedSync-Linux-${{ github.ref_name }}.tar.gz" \
-C linux-ubuntu/target/release interlinedlist-sync
# -------------------------------------------------------------------
# Snap packaging — M7
# Deferred from CI for now: snap-in-snap install of core22 fails
# inside GitHub-hosted runners (snapd restrictions in destructive
# mode). Reach parity by building the snap on a maintainer machine
# or via snapcraft remote-build, then uploading manually. See
# linux-ubuntu/PACKAGING.md and snap/snapcraft.yaml.
# -------------------------------------------------------------------
- name: Upload Linux artifacts
uses: actions/upload-artifact@v4
with:
name: linux-release
path: |
linux-ubuntu/target/debian/*.deb
linux-ubuntu/InterlinedSync-Linux-${{ github.ref_name }}.tar.gz
linux-ubuntu/*.snap
if-no-files-found: error
retention-days: 1
# ---------------------------------------------------------------------------
# Create a draft GitHub Release and attach all platform artifacts.
# Publish the release manually from the GitHub UI after reviewing the files.
# ---------------------------------------------------------------------------
create-release:
name: Create GitHub Release
needs: [build-macos, build-windows, build-linux]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Download all release artifacts
uses: actions/download-artifact@v4
with:
path: release-artifacts
merge-multiple: true
- name: List artifacts
run: ls -lh release-artifacts/
- name: Write release notes
run: |
TAG="${{ github.ref_name }}"
cat > release-notes.md << EOF
## InterlinedList Sync ${TAG}
### Downloads
| Platform | File | Notes |
|----------|------|-------|
| macOS 13 Ventura or later | \`InterlinedSync-macOS-${TAG}.zip\` | Unzip and move to \`/Applications\`. Unsigned — right-click → Open to bypass Gatekeeper on first launch. |
| Windows 10 / 11 (x64) — installer | \`InterlinedListSync-Setup-${TAG}.exe\` | Double-click and follow the wizard. Adds a Start Menu entry and optional autostart. **Self-contained** — no separate .NET runtime is required. |
| Windows 10 / 11 (x64) — portable | \`InterlinedSync-Windows-${TAG}.zip\` | Unzip and run \`InterlinedSync.exe\` for users who prefer no installer. **Self-contained** — no separate .NET runtime is required. |
| Ubuntu / Debian (amd64) | \`interlinedlist-sync_*.deb\` | \`sudo dpkg -i interlinedlist-sync_*.deb\` |
| Linux standalone binary | \`InterlinedSync-Linux-${TAG}.tar.gz\` | Extract and place \`interlinedlist-sync\` in your \`\$PATH\`. |
### First-time setup
1. Edit \`~/.config/interlinedlist-sync/config.toml\` — set \`sync.watched_dirs\` and \`network.api_base_url\`.
2. Store credentials once: \`interlinedlist-sync --login --username you@example.com --password …\`
3. Start the daemon: \`interlinedlist-sync --daemon\` (Linux: managed by the installed systemd user unit)
### What changed
See the commit history for details.
EOF
- name: Create draft GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
PRERELEASE_FLAG=""
if [[ "${{ github.ref_name }}" == *-* ]]; then
PRERELEASE_FLAG="--prerelease"
fi
# Use find -type f to enumerate only files; the upload-artifact
# action preserves the source directory structure inside each
# artifact zip (e.g. target/debian/*.deb -> release-artifacts/target/),
# and gh release create refuses to upload directories.
gh release create "${{ github.ref_name }}" \
--title "InterlinedList Sync ${{ github.ref_name }}" \
--notes-file release-notes.md \
--draft \
$PRERELEASE_FLAG \
$(find release-artifacts -type f)