-
Notifications
You must be signed in to change notification settings - Fork 2
638 lines (539 loc) · 21.5 KB
/
Copy pathrelease.yml
File metadata and controls
638 lines (539 loc) · 21.5 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
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
name: Release
on:
push:
tags:
- "v*.*.*" # Trigger on version tags like v1.0.0, v0.1.2, etc.
permissions:
contents: write # Required for creating releases
packages: write # Required for GHCR Docker publishing
env:
CARGO_TERM_COLOR: always
RUST_VERSION: "1.90.0" # Pin Rust version for reproducible builds
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# Build webapps once and share across all platforms
build-webapps:
name: Build Web Applications
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Build dashboard webapp
run: |
cd webapps/dashboard
export VITE_API_BASE_URL=""
bun install
bun run build
- name: Build exit-node-portal webapp
run: |
cd webapps/exit-node-portal
export VITE_API_BASE_URL=""
bun install
bun run build
- name: Upload webapp artifacts
uses: actions/upload-artifact@v4
with:
name: webapps
path: |
webapps/dashboard/dist
webapps/exit-node-portal/dist
retention-days: 1
# Matrix build for all platforms
build-binaries:
name: Build ${{ matrix.target }}
needs: build-webapps
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# Linux AMD64
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
archive_name: linux-amd64
strip: strip
# Linux ARM64
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
archive_name: linux-arm64
strip: aarch64-linux-gnu-strip
cross: true
# macOS AMD64 (Intel) - Cross-compiled on Apple Silicon runner
- target: x86_64-apple-darwin
os: macos-14 # Apple Silicon runner (cross-compile for Intel)
archive_name: macos-amd64
strip: strip
# macOS ARM64 (Apple Silicon)
- target: aarch64-apple-darwin
os: macos-14 # Apple Silicon runner (native)
archive_name: macos-arm64
strip: strip
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Extract version from tag
id: get_version
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
shell: bash
- name: Download webapp artifacts
uses: actions/download-artifact@v4
with:
name: webapps
path: webapps/
# Install Bun (required by localup-client build.rs)
- name: Install Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.RUST_VERSION }}
targets: ${{ matrix.target }}
# Linux ARM64 requires cross-compilation tools
- name: Install cross-compilation tools (Linux ARM64)
if: matrix.cross == true
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
# Use Swatinem/rust-cache for proper cross-platform Rust caching
# This caches ~/.cargo and target/ directories intelligently
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
# Share cargo registry/index across all targets (huge savings)
# Only target/ is per-target (necessary due to different architectures)
shared-key: "release"
cache-targets: true
cache-on-failure: true
# Cache key includes: OS, target, Cargo.lock hash, and rust version
# Automatically handles cross-platform paths (works on Windows/Linux/macOS)
# Build binaries from target directory
- name: Build binaries
env:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: ${{ matrix.cross == true && 'aarch64-linux-gnu-gcc' || '' }}
CC_aarch64_unknown_linux_gnu: ${{ matrix.cross == true && 'aarch64-linux-gnu-gcc' || '' }}
AR_aarch64_unknown_linux_gnu: ${{ matrix.cross == true && 'aarch64-linux-gnu-ar' || '' }}
LOCALUP_VERSION: ${{ steps.get_version.outputs.VERSION }}
run: |
cargo build --release --target ${{ matrix.target }} -p localup-cli
# Strip binaries
- name: Strip binaries
run: |
${{ matrix.strip }} target/${{ matrix.target }}/release/localup
# Create release archives (tar.gz)
- name: Create release archives
run: |
mkdir -p release
# Package localup CLI
tar -czf release/localup-${{ matrix.archive_name }}.tar.gz \
-C target/${{ matrix.target }}/release localup
# Create checksums
cd release
shasum -a 256 *.tar.gz > checksums-${{ matrix.archive_name }}.txt
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.archive_name }}-binaries
path: release/*
retention-days: 1
# Build Tauri desktop app for all platforms
build-tauri:
name: Build Desktop App (${{ matrix.platform }})
needs: build-webapps
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# macOS ARM64 (Apple Silicon)
- platform: macos-arm64
os: macos-14
target: aarch64-apple-darwin
bundle_targets: dmg
# macOS AMD64 (Intel)
- platform: macos-amd64
os: macos-14
target: x86_64-apple-darwin
bundle_targets: dmg
# Linux AMD64
- platform: linux-amd64
os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
bundle_targets: appimage,deb
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Extract version from tag
id: get_version
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
shell: bash
- name: Install Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.RUST_VERSION }}
targets: ${{ matrix.target }}
# Linux dependencies for Tauri
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
librsvg2-dev \
patchelf \
libssl-dev \
libgtk-3-dev \
libayatana-appindicator3-dev
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
shared-key: "tauri-${{ matrix.platform }}"
cache-targets: true
cache-on-failure: true
# Install frontend dependencies and build frontend
- name: Install frontend dependencies
working-directory: apps/localup-desktop
run: |
bun install
bun run build
# Import Apple certificate (macOS only)
- name: Import Apple certificate
if: runner.os == 'macOS'
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
if [ -n "$APPLE_CERTIFICATE" ]; then
echo "Importing Apple certificate..."
CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
# Decode certificate
echo -n "$APPLE_CERTIFICATE" | base64 --decode -o $CERTIFICATE_PATH
# Create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# Import certificate
security import $CERTIFICATE_PATH -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
echo "Certificate imported successfully"
else
echo "No Apple certificate configured, skipping code signing"
fi
# Build Tauri app
- name: Build Tauri app
working-directory: apps/localup-desktop
env:
LOCALUP_VERSION: ${{ steps.get_version.outputs.VERSION }}
# Tauri updater signing
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
# macOS code signing - skip if not configured
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY || '-' }}
# macOS notarization
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
# Skip code signing if APPLE_SIGNING_IDENTITY is not set (use '-' to disable)
if [ "$APPLE_SIGNING_IDENTITY" = "-" ] || [ -z "$APPLE_SIGNING_IDENTITY" ]; then
echo "⚠️ No Apple signing identity configured, building without code signing"
export APPLE_SIGNING_IDENTITY="-"
fi
bun run tauri build --target ${{ matrix.target }} --bundles ${{ matrix.bundle_targets }}
# Collect and rename artifacts for upload
- name: Collect artifacts
run: |
mkdir -p release
# Find where Tauri put the bundles (workspace target or local target)
BUNDLE_DIR="target/${{ matrix.target }}/release/bundle"
if [ ! -d "$BUNDLE_DIR" ]; then
BUNDLE_DIR="apps/localup-desktop/src-tauri/target/${{ matrix.target }}/release/bundle"
fi
echo "Looking for bundles in: $BUNDLE_DIR"
ls -laR "$BUNDLE_DIR" 2>/dev/null || echo "Bundle dir not found"
# macOS DMG + signature
if ls "$BUNDLE_DIR/dmg/"*.dmg 1> /dev/null 2>&1; then
for f in "$BUNDLE_DIR/dmg/"*.dmg; do
cp "$f" "release/LocalUp-${{ matrix.platform }}.dmg"
done
fi
if ls "$BUNDLE_DIR/dmg/"*.dmg.sig 1> /dev/null 2>&1; then
for f in "$BUNDLE_DIR/dmg/"*.dmg.sig; do
cp "$f" "release/LocalUp-${{ matrix.platform }}.dmg.sig"
done
fi
# Linux AppImage + signature (tar.gz format for updater)
if ls "$BUNDLE_DIR/appimage/"*.AppImage 1> /dev/null 2>&1; then
for f in "$BUNDLE_DIR/appimage/"*.AppImage; do
cp "$f" "release/LocalUp-${{ matrix.platform }}.AppImage"
done
fi
if ls "$BUNDLE_DIR/appimage/"*.AppImage.sig 1> /dev/null 2>&1; then
for f in "$BUNDLE_DIR/appimage/"*.AppImage.sig; do
cp "$f" "release/LocalUp-${{ matrix.platform }}.AppImage.sig"
done
fi
# Also check for tar.gz (Tauri updater format)
if ls "$BUNDLE_DIR/appimage/"*.AppImage.tar.gz 1> /dev/null 2>&1; then
for f in "$BUNDLE_DIR/appimage/"*.AppImage.tar.gz; do
cp "$f" "release/LocalUp-${{ matrix.platform }}.AppImage.tar.gz"
done
fi
if ls "$BUNDLE_DIR/appimage/"*.AppImage.tar.gz.sig 1> /dev/null 2>&1; then
for f in "$BUNDLE_DIR/appimage/"*.AppImage.tar.gz.sig; do
cp "$f" "release/LocalUp-${{ matrix.platform }}.AppImage.tar.gz.sig"
done
fi
# Linux deb
if ls "$BUNDLE_DIR/deb/"*.deb 1> /dev/null 2>&1; then
for f in "$BUNDLE_DIR/deb/"*.deb; do
cp "$f" "release/LocalUp-${{ matrix.platform }}.deb"
done
fi
# Create checksums for desktop artifacts
cd release
if ls *.dmg *.AppImage *.deb 1> /dev/null 2>&1; then
shasum -a 256 *.dmg *.AppImage *.deb 2>/dev/null > checksums-desktop-${{ matrix.platform }}.txt || true
fi
ls -lah
- name: Upload Tauri artifacts
uses: actions/upload-artifact@v4
with:
name: desktop-${{ matrix.platform }}
path: release/*
retention-days: 1
# Create GitHub Release with all artifacts
# Only requires CLI binaries - desktop apps are optional
create-release:
name: Create GitHub Release
needs: [build-binaries]
if: always() && needs.build-binaries.result == 'success'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts/
continue-on-error: true # Desktop artifacts may not exist if build-tauri failed
- name: Consolidate release files
run: |
mkdir -p release
# CLI binaries
find artifacts/ -type f \( -name "*.tar.gz" -o -name "checksums-*.txt" \) \
-exec cp {} release/ \;
# Desktop app artifacts (DMG, AppImage, deb) and their signatures
find artifacts/ -type f \( -name "*.dmg" -o -name "*.dmg.sig" -o -name "*.AppImage" -o -name "*.AppImage.sig" -o -name "*.AppImage.tar.gz" -o -name "*.AppImage.tar.gz.sig" -o -name "*.deb" \) \
-exec cp {} release/ \;
# Create a combined checksums file
cd release
if ls checksums-*.txt 1> /dev/null 2>&1; then
cat checksums-*.txt > SHA256SUMS.txt
fi
cd ..
ls -lah release/
- name: Extract version from tag
id: get_version
run: |
VERSION=${GITHUB_REF#refs/tags/}
VERSION_NUM=${VERSION#v}
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "VERSION_NUM=$VERSION_NUM" >> $GITHUB_OUTPUT
# Detect if this is a pre-release (contains alpha, beta, rc, or has dash followed by non-numeric)
if [[ "$VERSION" =~ (alpha|beta|rc|-[a-zA-Z]) ]]; then
echo "IS_PRERELEASE=true" >> $GITHUB_OUTPUT
else
echo "IS_PRERELEASE=false" >> $GITHUB_OUTPUT
fi
- name: Create latest.json for auto-updater
run: |
# Only create latest.json if we have desktop artifacts
if ! ls release/*.dmg release/*.AppImage 1> /dev/null 2>&1; then
echo "No desktop artifacts found, skipping latest.json creation"
exit 0
fi
VERSION="${{ steps.get_version.outputs.VERSION_NUM }}"
RELEASE_URL="https://github.com/localup-dev/localup/releases/download/${{ steps.get_version.outputs.VERSION }}"
PUB_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
# Read signatures from .sig files
MACOS_ARM64_SIG=""
MACOS_AMD64_SIG=""
LINUX_AMD64_SIG=""
if [ -f "release/LocalUp-macos-arm64.dmg.sig" ]; then
MACOS_ARM64_SIG=$(cat release/LocalUp-macos-arm64.dmg.sig)
fi
if [ -f "release/LocalUp-macos-amd64.dmg.sig" ]; then
MACOS_AMD64_SIG=$(cat release/LocalUp-macos-amd64.dmg.sig)
fi
if [ -f "release/LocalUp-linux-amd64.AppImage.tar.gz.sig" ]; then
LINUX_AMD64_SIG=$(cat release/LocalUp-linux-amd64.AppImage.tar.gz.sig)
fi
# Create latest.json
cat > release/latest.json << EOF
{
"version": "${VERSION}",
"notes": "See the release notes at ${RELEASE_URL}",
"pub_date": "${PUB_DATE}",
"platforms": {
"darwin-aarch64": {
"url": "${RELEASE_URL}/LocalUp-macos-arm64.dmg",
"signature": "${MACOS_ARM64_SIG}"
},
"darwin-x86_64": {
"url": "${RELEASE_URL}/LocalUp-macos-amd64.dmg",
"signature": "${MACOS_AMD64_SIG}"
},
"linux-x86_64": {
"url": "${RELEASE_URL}/LocalUp-linux-amd64.AppImage.tar.gz",
"signature": "${LINUX_AMD64_SIG}"
}
}
}
EOF
echo "Created latest.json:"
cat release/latest.json
- name: Create Release
uses: softprops/action-gh-release@v1
with:
name: Release ${{ steps.get_version.outputs.VERSION }}
draft: false
prerelease: ${{ steps.get_version.outputs.IS_PRERELEASE }}
generate_release_notes: true
body: |
## Desktop App
Download the LocalUp desktop application for GUI-based tunnel management:
### macOS
- **Apple Silicon (M1/M2/M3)**: `LocalUp-macos-arm64.dmg`
- **Intel**: `LocalUp-macos-amd64.dmg`
> **Note for macOS users:** The app is not yet code-signed. If you see _"LocalUp is damaged and can't be opened"_, run this command after installing:
> ```bash
> xattr -cr /Applications/LocalUp.app
> ```
> Then open the app again.
### Linux
- **AppImage (Universal)**: `LocalUp-linux-amd64.AppImage`
- **Debian/Ubuntu**: `LocalUp-linux-amd64.deb`
---
## CLI Tool
### Homebrew (macOS/Linux)
**Note:** The Homebrew formula needs to be updated manually after this release.
**For Maintainers:**
```bash
# Update the formula (interactive)
./scripts/manual-formula-update.sh
# Or quick update
./scripts/quick-formula-update.sh ${{ steps.get_version.outputs.VERSION }}
```
**For Users (after formula is updated):**
```bash
${{ steps.get_version.outputs.IS_PRERELEASE == 'true' && '# BETA/PRE-RELEASE' || '# Stable release' }}
${{ steps.get_version.outputs.IS_PRERELEASE == 'true' && 'brew install https://raw.githubusercontent.com/localup-dev/localup/main/Formula/localup-beta.rb' || 'brew tap localup-dev/tap https://github.com/localup-dev/localup' }}
${{ steps.get_version.outputs.IS_PRERELEASE == 'false' && 'brew install localup' || '' }}
```
### Manual Download
Download the command-line tool for your platform:
#### Linux
- **AMD64**: `localup-linux-amd64.tar.gz`
- **ARM64**: `localup-linux-arm64.tar.gz`
#### macOS
- **Intel (AMD64)**: `localup-macos-amd64.tar.gz`
- **Apple Silicon (ARM64)**: `localup-macos-arm64.tar.gz`
---
## Verify Downloads
All archives include SHA256 checksums. Verify your download:
```bash
sha256sum -c SHA256SUMS.txt
# Or check individual file
shasum -a 256 <filename>
grep <filename> SHA256SUMS.txt
```
## Install CLI
```bash
tar -xzf localup-<platform>-<arch>.tar.gz
sudo mv localup /usr/local/bin/
chmod +x /usr/local/bin/localup
```
files: |
release/*.tar.gz
release/*.dmg
release/*.dmg.sig
release/*.AppImage
release/*.AppImage.tar.gz
release/*.AppImage.tar.gz.sig
release/*.deb
release/checksums-*.txt
release/SHA256SUMS.txt
release/latest.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Build and push Docker image to GHCR
# docker-publish:
# name: Publish Docker Image to GHCR
# needs: create-release
# runs-on: ubuntu-latest
# steps:
# - name: Checkout code
# uses: actions/checkout@v4
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v3
# - name: Log in to Container Registry
# uses: docker/login-action@v3
# with:
# registry: ${{ env.REGISTRY }}
# username: ${{ github.actor }}
# password: ${{ secrets.GITHUB_TOKEN }}
# - name: Extract metadata
# id: meta
# uses: docker/metadata-action@v5
# with:
# images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# tags: |
# type=semver,pattern={{version}}
# type=semver,pattern={{major}}.{{minor}}
# type=semver,pattern={{major}}
# type=sha,prefix=sha-
# type=raw,value=latest
# - name: Build and push Docker image
# uses: docker/build-push-action@v5
# with:
# context: .
# file: ./Dockerfile
# push: true
# tags: ${{ steps.meta.outputs.tags }}
# labels: ${{ steps.meta.outputs.labels }}
# cache-from: type=gha
# cache-to: type=gha,mode=max
# - name: Test Docker image
# run: |
# echo "Testing Docker image..."
# docker build -f Dockerfile -t localup:test .
# docker run --rm localup:test --version
# docker run --rm localup:test --help
# - name: Print image details
# run: |
# echo "## Docker Image Published to GHCR ✅"
# echo ""
# echo "### Build Metadata"
# echo "**Images:**"
# echo "${{ steps.meta.outputs.tags }}"
# echo ""
# echo "**Labels:**"
# echo "${{ steps.meta.outputs.labels }}"