Skip to content

Commit e7d05e1

Browse files
Create reproducible-build-check-release.yml
1 parent 3fa597e commit e7d05e1

1 file changed

Lines changed: 183 additions & 0 deletions

File tree

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
name: Reproducible Build Check (master)
2+
3+
on:
4+
push:
5+
branches: [ "master" ]
6+
7+
jobs:
8+
build-1:
9+
name: Build 1 (signed)
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout repository
13+
uses: actions/checkout@v4
14+
15+
- name: Decode keystore
16+
run: |
17+
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > ${{ github.workspace }}/release.keystore
18+
19+
- name: Build Docker image
20+
working-directory: ci
21+
run: docker build -t deku_rep_build_release .
22+
23+
- name: Build unsigned APK
24+
run: |
25+
docker run --rm \
26+
-v "$(pwd)":/project \
27+
-w /project \
28+
--user "$(id -u):$(id -g)" \
29+
-e ANDROID_USER_HOME=/project/.android \
30+
-e GRADLE_USER_HOME=/project/.gradle \
31+
deku_rep_build_release \
32+
./gradlew assembleRelease \
33+
--no-daemon \
34+
--max-workers=2 \
35+
--console=plain \
36+
-Dorg.gradle.jvmargs="-Xmx2048m -Xms512m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8" \
37+
-Dkotlin.daemon.jvm.options="-Xmx512m,-Xss1m" \
38+
-Dkotlin.compiler.execution.strategy=in-process
39+
40+
- name: Sign APK with apksigner
41+
run: |
42+
BUILD_TOOLS=$(ls $ANDROID_HOME/build-tools | sort -V | tail -1)
43+
$ANDROID_HOME/build-tools/$BUILD_TOOLS/apksigner sign \
44+
--ks ${{ github.workspace }}/release.keystore \
45+
--ks-key-alias "${{ secrets.KEY_ALIAS }}" \
46+
--ks-pass pass:"${{ secrets.STORE_PASSWORD }}" \
47+
--key-pass pass:"${{ secrets.KEY_PASSWORD }}" \
48+
--out app/build/outputs/apk/release/app-release-signed.apk \
49+
app/build/outputs/apk/release/app-release-unsigned.apk
50+
51+
- name: Verify signature
52+
run: |
53+
BUILD_TOOLS=$(ls $ANDROID_HOME/build-tools | sort -V | tail -1)
54+
$ANDROID_HOME/build-tools/$BUILD_TOOLS/apksigner verify \
55+
--verbose \
56+
app/build/outputs/apk/release/app-release-signed.apk
57+
58+
- name: Upload signed APK
59+
uses: actions/upload-artifact@v4
60+
with:
61+
name: apk-build-1
62+
path: app/build/outputs/apk/release/app-release-signed.apk
63+
retention-days: 1
64+
65+
build-2:
66+
name: Build 2 (signed)
67+
runs-on: ubuntu-latest
68+
steps:
69+
- name: Checkout repository
70+
uses: actions/checkout@v4
71+
72+
- name: Decode keystore
73+
run: |
74+
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > ${{ github.workspace }}/release.keystore
75+
76+
- name: Build Docker image
77+
working-directory: ci
78+
run: docker build -t deku_rep_build_release .
79+
80+
- name: Build unsigned APK
81+
run: |
82+
docker run --rm \
83+
-v "$(pwd)":/project \
84+
-w /project \
85+
--user "$(id -u):$(id -g)" \
86+
-e ANDROID_USER_HOME=/project/.android \
87+
-e GRADLE_USER_HOME=/project/.gradle \
88+
deku_rep_build_release \
89+
./gradlew assembleRelease \
90+
--no-daemon \
91+
--max-workers=2 \
92+
--console=plain \
93+
-Dorg.gradle.jvmargs="-Xmx2048m -Xms512m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8" \
94+
-Dkotlin.daemon.jvm.options="-Xmx512m,-Xss1m" \
95+
-Dkotlin.compiler.execution.strategy=in-process
96+
97+
- name: Sign APK with apksigner
98+
run: |
99+
BUILD_TOOLS=$(ls $ANDROID_HOME/build-tools | sort -V | tail -1)
100+
$ANDROID_HOME/build-tools/$BUILD_TOOLS/apksigner sign \
101+
--ks ${{ github.workspace }}/release.keystore \
102+
--ks-key-alias "${{ secrets.KEY_ALIAS }}" \
103+
--ks-pass pass:"${{ secrets.STORE_PASSWORD }}" \
104+
--key-pass pass:"${{ secrets.KEY_PASSWORD }}" \
105+
--out app/build/outputs/apk/release/app-release-signed.apk \
106+
app/build/outputs/apk/release/app-release-unsigned.apk
107+
108+
- name: Verify signature
109+
run: |
110+
BUILD_TOOLS=$(ls $ANDROID_HOME/build-tools | sort -V | tail -1)
111+
$ANDROID_HOME/build-tools/$BUILD_TOOLS/apksigner verify \
112+
--verbose \
113+
app/build/outputs/apk/release/app-release-signed.apk
114+
115+
- name: Upload signed APK
116+
uses: actions/upload-artifact@v4
117+
with:
118+
name: apk-build-2
119+
path: app/build/outputs/apk/release/app-release-signed.apk
120+
retention-days: 1
121+
122+
compare:
123+
name: Compare signed APKs
124+
runs-on: ubuntu-latest
125+
needs: [ build-1, build-2 ]
126+
steps:
127+
- name: Download APK from build 1
128+
uses: actions/download-artifact@v4
129+
with:
130+
name: apk-build-1
131+
path: apk-build-1
132+
133+
- name: Download APK from build 2
134+
uses: actions/download-artifact@v4
135+
with:
136+
name: apk-build-2
137+
path: apk-build-2
138+
139+
- name: Compare hashes
140+
id: compare
141+
run: |
142+
SHA1=$(sha256sum apk-build-1/app-release-signed.apk | awk '{ print $1 }')
143+
SHA2=$(sha256sum apk-build-2/app-release-signed.apk | awk '{ print $1 }')
144+
echo "Build 1: $SHA1"
145+
echo "Build 2: $SHA2"
146+
if [ "$SHA1" = "$SHA2" ]; then
147+
echo "Reproducible build verified — hashes match."
148+
echo "reproducible=true" >> "$GITHUB_OUTPUT"
149+
else
150+
echo "Build is NOT reproducible — hashes differ!"
151+
echo "reproducible=false" >> "$GITHUB_OUTPUT"
152+
fi
153+
154+
- name: Install diffoscope
155+
if: steps.compare.outputs.reproducible == 'false'
156+
run: |
157+
sudo apt-get update -qq
158+
sudo apt-get install -y diffoscope
159+
160+
- name: Run diffoscope
161+
if: steps.compare.outputs.reproducible == 'false'
162+
run: |
163+
diffoscope \
164+
--text diffoscope-report.txt \
165+
--html diffoscope-report.html \
166+
apk-build-1/app-release-signed.apk \
167+
apk-build-2/app-release-signed.apk || true
168+
169+
- name: Upload diffoscope report
170+
if: steps.compare.outputs.reproducible == 'false'
171+
uses: actions/upload-artifact@v4
172+
with:
173+
name: diffoscope-report
174+
path: |
175+
diffoscope-report.txt
176+
diffoscope-report.html
177+
retention-days: 7
178+
179+
- name: Fail if not reproducible
180+
if: steps.compare.outputs.reproducible == 'false'
181+
run: |
182+
echo "See the diffoscope-report artifact for a full breakdown of differences."
183+
exit 1

0 commit comments

Comments
 (0)