-
Notifications
You must be signed in to change notification settings - Fork 2
237 lines (202 loc) · 10.7 KB
/
release.yml
File metadata and controls
237 lines (202 loc) · 10.7 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
name: Production Release
on:
push:
tags:
- 'v*'
# Group by tag ref — re-pushing the same tag cancels any in-flight rebuild
# for that tag, but distinct version tags pushed back-to-back each get to
# complete and publish their own release.
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true
jobs:
release:
name: Build & Publish Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
api-level: 34
build-tools: 34.0.0
cmake: 3.22.1
- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Decode and setup keystore
run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > keystore.jks
echo "✅ Keystore decoded and ready"
- name: Run all tests
run: ./gradlew test --no-daemon
- name: Run security scan
run: ./gradlew dependencyCheckAnalyze --no-daemon
- name: Verify test coverage
run: ./gradlew jacocoTestReport jacocoTestCoverageVerification --no-daemon
- name: Get release version
id: version
run: |
# Extract tag name and remove 'v' prefix if present
TAG_NAME=${GITHUB_REF#refs/tags/}
VERSION=${TAG_NAME#v}
echo "TAG_NAME=$TAG_NAME" >> $GITHUB_OUTPUT
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "RELEASE_NAME=tabssh-android-arm64" >> $GITHUB_OUTPUT
echo "📦 Building release for tag: $TAG_NAME"
echo "📦 Version: $VERSION"
- name: Fetch latest mosh-client binaries
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Pulls latest mosh-X.Y.Z release assets from this repo and drops
# them into app/src/main/jniLibs/<abi>/libmosh-client.so. Won't fail
# the build if no release exists yet (workflow `mosh-binaries.yml`
# publishes them on a monthly cadence).
chmod +x scripts/fetch-mosh-binaries.sh
TABSSH_REPO="${{ github.repository }}" scripts/fetch-mosh-binaries.sh
- name: Build release APK
run: ./gradlew assembleRelease
env:
RELEASE_BUILD: true
- name: Process multi-architecture APKs
run: |
cd app/build/outputs/apk/release/
echo "🏗️ Processing architecture-specific APKs..."
# Handle APK splits generated by Gradle
for apk in *.apk; do
echo "Processing: $apk"
if [[ "$apk" == *"arm64"* ]]; then
cp "$apk" "tabssh-android-arm64-${{ steps.version.outputs.VERSION }}.apk"
elif [[ "$apk" == *"armeabi"* ]]; then
cp "$apk" "tabssh-android-arm-${{ steps.version.outputs.VERSION }}.apk"
elif [[ "$apk" == *"x86_64"* ]]; then
cp "$apk" "tabssh-android-amd64-${{ steps.version.outputs.VERSION }}.apk"
elif [[ "$apk" == *"x86"* ]] && [[ "$apk" != *"x86_64"* ]]; then
cp "$apk" "tabssh-android-x86-${{ steps.version.outputs.VERSION }}.apk"
elif [[ "$apk" == *"universal"* ]]; then
cp "$apk" "tabssh-android-universal-${{ steps.version.outputs.VERSION }}.apk"
fi
done
- name: Build F-Droid compatible APK
run: ./gradlew assembleFdroidRelease --no-daemon
- name: Copy and rename F-Droid APKs
run: |
cd app/build/outputs/apk/fdroidRelease/
echo "🏗️ Processing F-Droid APKs..."
ls -lh *.apk
# Rename APKs with version suffix
for apk in tabssh-*.apk; do
if [[ "$apk" == *"arm64-v8a"* ]]; then
cp "$apk" "tabssh-android-arm64-fdroid-${{ steps.version.outputs.VERSION }}.apk"
elif [[ "$apk" == *"armeabi-v7a"* ]]; then
cp "$apk" "tabssh-android-arm-fdroid-${{ steps.version.outputs.VERSION }}.apk"
elif [[ "$apk" == *"x86_64"* ]]; then
cp "$apk" "tabssh-android-amd64-fdroid-${{ steps.version.outputs.VERSION }}.apk"
elif [[ "$apk" == *"x86"* ]] && [[ "$apk" != *"x86_64"* ]]; then
cp "$apk" "tabssh-android-x86-fdroid-${{ steps.version.outputs.VERSION }}.apk"
elif [[ "$apk" == *"universal"* ]]; then
cp "$apk" "tabssh-android-universal-fdroid-${{ steps.version.outputs.VERSION }}.apk"
fi
done
echo "✅ F-Droid APKs renamed:"
ls -lh tabssh-android-*-fdroid-*.apk
- name: Generate release notes
run: |
echo "# TabSSH ${{ steps.version.outputs.VERSION }} - Complete Mobile SSH Client" > RELEASE_NOTES.md
echo "" >> RELEASE_NOTES.md
echo "🎉 **Complete 1.0.0 Feature Set - Everything Included!**" >> RELEASE_NOTES.md
echo "" >> RELEASE_NOTES.md
echo "## 📦 Downloads" >> RELEASE_NOTES.md
echo "- **tabssh-android-arm64-${{ steps.version.outputs.VERSION }}.apk** - ARM64 release (recommended for most devices)" >> RELEASE_NOTES.md
echo "- **tabssh-android-arm-${{ steps.version.outputs.VERSION }}.apk** - ARM release (older devices)" >> RELEASE_NOTES.md
echo "- **tabssh-android-amd64-${{ steps.version.outputs.VERSION }}.apk** - x86_64 release (emulators, Chromebooks)" >> RELEASE_NOTES.md
echo "- **tabssh-android-arm64-fdroid-${{ steps.version.outputs.VERSION }}.apk** - F-Droid ARM64 version" >> RELEASE_NOTES.md
echo "- **tabssh-android-arm-fdroid-${{ steps.version.outputs.VERSION }}.apk** - F-Droid ARM version" >> RELEASE_NOTES.md
echo "- **tabssh-android-amd64-fdroid-${{ steps.version.outputs.VERSION }}.apk** - F-Droid x86_64 version" >> RELEASE_NOTES.md
echo "" >> RELEASE_NOTES.md
cat CHANGELOG.md >> RELEASE_NOTES.md
- name: Generate SHA checksums
run: |
echo "🔐 Generating SHA256 checksums for security verification..."
cd app/build/outputs/apk/release/
sha256sum tabssh-android-*.apk > tabssh-android-checksums-${{ steps.version.outputs.VERSION }}.sha256
echo "## 🔐 Release APK Checksums" >> ../../../../../RELEASE_NOTES.md
echo "\`\`\`" >> ../../../../../RELEASE_NOTES.md
cat tabssh-android-checksums-${{ steps.version.outputs.VERSION }}.sha256 >> ../../../../../RELEASE_NOTES.md
echo "\`\`\`" >> ../../../../../RELEASE_NOTES.md
echo "" >> ../../../../../RELEASE_NOTES.md
cd ../fdroidRelease/
sha256sum tabssh-android-*-fdroid-*.apk > tabssh-android-fdroid-checksums-${{ steps.version.outputs.VERSION }}.sha256
echo "## 🔐 F-Droid APK Checksums" >> ../../../../../RELEASE_NOTES.md
echo "\`\`\`" >> ../../../../../RELEASE_NOTES.md
cat tabssh-android-fdroid-checksums-${{ steps.version.outputs.VERSION }}.sha256 >> ../../../../../RELEASE_NOTES.md
echo "\`\`\`" >> ../../../../../RELEASE_NOTES.md
echo "" >> ../../../../../RELEASE_NOTES.md
echo "**Verify downloads**: \`sha256sum -c tabssh-android-checksums-${{ steps.version.outputs.VERSION }}.sha256\`" >> ../../../../../RELEASE_NOTES.md
- name: Delete existing release if exists
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "🗑️ Checking for existing release: ${{ steps.version.outputs.TAG_NAME }}"
# Try to delete the release (both with and without 'v' prefix)
gh release delete ${{ steps.version.outputs.TAG_NAME }} --yes || echo "Release ${{ steps.version.outputs.TAG_NAME }} not found"
gh release delete v${{ steps.version.outputs.VERSION }} --yes || echo "Release v${{ steps.version.outputs.VERSION }} not found"
gh release delete ${{ steps.version.outputs.VERSION }} --yes || echo "Release ${{ steps.version.outputs.VERSION }} not found"
echo "✅ Cleanup complete, ready to create new release"
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
name: "TabSSH ${{ steps.version.outputs.VERSION }} - Complete Mobile SSH Client"
files: |
app/build/outputs/apk/release/tabssh-android-arm64-${{ steps.version.outputs.VERSION }}.apk
app/build/outputs/apk/release/tabssh-android-arm-${{ steps.version.outputs.VERSION }}.apk
app/build/outputs/apk/release/tabssh-android-amd64-${{ steps.version.outputs.VERSION }}.apk
app/build/outputs/apk/release/tabssh-android-x86-${{ steps.version.outputs.VERSION }}.apk
app/build/outputs/apk/release/tabssh-android-universal-${{ steps.version.outputs.VERSION }}.apk
app/build/outputs/apk/fdroidRelease/tabssh-android-arm64-fdroid-${{ steps.version.outputs.VERSION }}.apk
app/build/outputs/apk/fdroidRelease/tabssh-android-arm-fdroid-${{ steps.version.outputs.VERSION }}.apk
app/build/outputs/apk/fdroidRelease/tabssh-android-amd64-fdroid-${{ steps.version.outputs.VERSION }}.apk
app/build/outputs/apk/fdroidRelease/tabssh-android-x86-fdroid-${{ steps.version.outputs.VERSION }}.apk
app/build/outputs/apk/fdroidRelease/tabssh-android-universal-fdroid-${{ steps.version.outputs.VERSION }}.apk
app/build/outputs/apk/release/tabssh-android-checksums-${{ steps.version.outputs.VERSION }}.sha256
app/build/outputs/apk/fdroidRelease/tabssh-android-fdroid-checksums-${{ steps.version.outputs.VERSION }}.sha256
app/build/outputs/mapping/release/mapping.txt
RELEASE_NOTES.md
metadata/io.github.tabssh.yml
body_path: RELEASE_NOTES.md
generate_release_notes: false
prerelease: false
draft: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare F-Droid submission package
run: |
mkdir -p fdroid-submission
cp app/build/outputs/apk/fdroidRelease/tabssh-android-arm64-fdroid-${{ steps.version.outputs.VERSION }}.apk fdroid-submission/
cp metadata/io.github.tabssh.yml fdroid-submission/
cp README.md fdroid-submission/
cp CHANGELOG.md fdroid-submission/
echo "F-Droid submission package ready in fdroid-submission/"
echo "Manual F-Droid submission required - package prepared."
- name: Notify community
run: |
# Send release notifications
./scripts/notify-release.sh "${{ github.ref_name }}"
env:
MATRIX_TOKEN: ${{ secrets.MATRIX_TOKEN }}
MASTODON_TOKEN: ${{ secrets.MASTODON_TOKEN }}