-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit af43d18
Showing
13 changed files
with
524 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
name: Actions | ||
|
||
on: | ||
schedule: | ||
- cron: '0 * * * *' # Runs every hour. | ||
# on: workflow_dispatch | ||
|
||
jobs: | ||
run-script: | ||
runs-on: ubuntu-latest | ||
env: | ||
HETZNER_API_TOKEN: ${{ secrets.HETZNER_API_TOKEN }} | ||
HETZNER_LOCATION: ${{ secrets.HETZNER_LOCATION }} | ||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} | ||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | ||
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} | ||
AWS_BUCKET_NAME: ${{ secrets.AWS_BUCKET_NAME }} | ||
PIXEL_CODENAMES: ${{ secrets.PIXEL_CODENAMES }} | ||
steps: | ||
- name: Checkout repository | ||
uses: actions/checkout@v2 | ||
- name: Run create_server.sh | ||
run: | | ||
bash ./infrastructure/hetzner/create_server.sh |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
## Disclaimer | ||
|
||
This project is a work in progress and will continue to evolve toward greater robustness, simplicity, and modularity. For now, don't take the results too seriously, and keep in mind that it is crucial that you know how to interpret them. | ||
|
||
I am NOT affiliated with the GrapheneOS Foundation. I am simply an individual who is interested in reproducible builds, particularly for this operating system that I run on my phone and will play an even larger role in my life in the future with the rumored Google Pixel Laptop. Based on everything I've observed so far, a recent Google Pixel (8th/9th gen) running GrapheneOS provides the most secure device and operating system that most people can have, so I decided to invest in reproducible builds for it myself. | ||
|
||
Reproducible builds help users ensure that the official release images match the published source code of a given software, providing transparency and fostering trust. | ||
|
||
This project utilizes cloud instances from Hetzner to perform the following: | ||
- Build GrapheneOS; | ||
- Unpack images, archives, and other special or unusual file types; | ||
- Images, archives, and other file types containing additional nested files are unpacked iteratively until everything is extracted; | ||
- The signatures of certain files and images are stripped to prevent them from interfering with the comparison process. | ||
- Compare the resulting reproduced build with the official build in order to see the differences; | ||
- Publish the diffoscope output, showcasing all the differences between files in the official builds and the reproduced builds. | ||
|
||
The fully automated reproducibility infrastructure will be triggered when a GitHub Actions workflow (running hourly) detects a new official release in the alpha channel. Official releases that hit the alpha channel usually make their way to stable within a few hours, so it's nice to reproduce them as soon as they hit alpha. | ||
|
||
The kernel and base OS are both compiled to ensure full OS reproducibility — no prebuilts are used, except for Vanadium and other apps (which I'll build soon). Vendor blobs are fetched directly from Google via `adevtool`, rather than from GrapheneOS repositories. | ||
|
||
Currently, this work is limited to the Google Pixel 8 Pro, which is the device I own. However, feel free to use this project with other devices. My scripts might be a bit messy at the moment, but it's a work in progress after all. This project should work with any supported Pixel device, using parameters defined by environment variables before you run the initial script. You can run it on your own infrastructure, given the right device names and cloud provider tokens. To know how to do so, check the [technical documentation](infrastructure/hetzner/README.md). | ||
|
||
### Results | ||
- The diffoscope reports are available at URLs that follow this pattern: | ||
- `https://gos-reproducibility-reports.s3.us-east-1.amazonaws.com/${PIXEL_CODENAME}-${BUILD_NUMBER}.html`. | ||
- For example: https://gos-reproducibility-reports.s3.us-east-1.amazonaws.com/husky-2024120900.html | ||
- The SHA512 hashes of the official builds that were tested/compared with are available at URLs that follow this pattern: | ||
- `https://gos-reproducibility-reports.s3.us-east-1.amazonaws.com/${PIXEL_CODENAME}-${BUILD_NUMBER}.checksums`. | ||
- For example: https://gos-reproducibility-reports.s3.us-east-1.amazonaws.com/husky-2024120900.checksums | ||
|
||
### Pending work | ||
- [ ] Build/reproduce Vanadium, App Store, Camera, PDF Viewer, TalkBack, GmsCompat, and Info; | ||
- [ ] Compare incremental and factory images too, instead of just ota_update and install images; | ||
- [ ] Consider using a cheaper instance type; | ||
- [ ] Save money by using an instance with IPv6 and no IPv4; | ||
- Cloning from GitHub wouldn't work, but there are some proxies available; | ||
- Uploading to AWS S3 wouldn't work; a workaround is required. | ||
- [ ] Sign the artifacts before uploading so that people can verify; | ||
- [ ] Wrap the scripts in Docker for those who want to run them locally. | ||
|
||
For more, read the "TODO:" lines inside the code. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
## Running | ||
To spin up a machine following my infrastructure scripts: | ||
1. Set the required environment variables (see the last section); | ||
2. Modify things as you wish; | ||
3. Run create_server.sh. It will get the latest GrapheneOS build number and start a machine to reproduce the build. | ||
|
||
Wait a few hours and check your bucket in order to see the reproducibility reports. | ||
|
||
### Environment variables | ||
Export the following variables in your shell with the appropriate values for your intentions and setup. If you are running in a CI environment, set them as secret environment variables: | ||
- `HETZNER_API_TOKEN="YOUR HETZNER API TOKEN"` | ||
- `HETZNER_LOCATION="THE DESIRED LOCATION"` | ||
- `AWS_BUCKET_NAME="THE NAME OF YOUR S3 BUCKET"` | ||
- `AWS_ACCESS_KEY_ID="YOUR AWS ACCESS KEY ID WITH PROPER S3 PERMISSIONS"` | ||
- `AWS_SECRET_ACCESS_KEY="YOUR AWS SECRET ACCESS KEY WITH PROPER S3 PERMISSIONS"` | ||
- `AWS_DEFAULT_REGION="THE AWS REGION WHERE YOUR BUCKET IS LOCATED"` | ||
- `AWS_BUCKET_NAME="THE AWS REGION WHERE YOUR BUCKET IS LOCATED"` | ||
- `PIXEL_CODENAMES="THE CODENAME(S) OF YOUR PIXEL DEVICE(S) (e.g. "bluejay husky tokay")"` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
#cloud-config | ||
timezone: US/Eastern | ||
users: | ||
- name: $NONROOT_USER | ||
shell: /bin/bash | ||
write_files: # I store the scripts inside /usr/local/bin for convenience as it is already part of PATH. | ||
- encoding: b64 | ||
content: ${STARTUP_SCRIPT_B64} | ||
owner: root:root | ||
path: /usr/local/bin/startup_script | ||
permissions: '0755' | ||
- encoding: b64 | ||
content: ${DELETE_SERVER_B64} | ||
owner: root:root | ||
path: /usr/local/bin/delete_server | ||
permissions: '0755' | ||
- encoding: b64 | ||
content: ${BUILD_GOS_B64} | ||
owner: root:root | ||
path: /usr/local/bin/build_gos | ||
permissions: '0755' | ||
- encoding: b64 | ||
content: ${DETECT_DEVICE_B64} | ||
owner: root:root | ||
path: /usr/local/bin/detect_device | ||
permissions: '0755' | ||
- encoding: b64 | ||
content: ${COMPARE_GOS_B64} | ||
owner: root:root | ||
path: /usr/local/bin/compare_gos | ||
permissions: '0755' | ||
- path: /etc/profile.d/custom_common_variables.sh | ||
content: | | ||
export PIXEL_CODENAME=$PIXEL_CODENAME | ||
export GOS_BUILD_NUMBER=$GOS_BUILD_NUMBER | ||
export GOS_BUILD_DATETIME=$GOS_BUILD_DATETIME | ||
export NONROOT_USER=$NONROOT_USER | ||
permissions: '0755' | ||
- path: /root/.sensitive_vars | ||
append: true | ||
content: | | ||
# Variables that should be exclusive to the root user. | ||
export HETZNER_API_TOKEN=$HETZNER_API_TOKEN | ||
export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID | ||
export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY | ||
export AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION | ||
export AWS_BUCKET_NAME=$AWS_BUCKET_NAME | ||
runcmd: | ||
- [ /usr/local/bin/startup_script ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
#!/bin/bash | ||
set -euo pipefail | ||
|
||
# ~/gos_create_server_secrets.sh exports some secrets. Useful when testing locally. Do not expose it. | ||
# On GitHub Actions, they're already set as secret environment variables. | ||
CURL_OUTPUT="-o /dev/null" | ||
FORCE_REPEAT_IF_ALREADY_REPRODUCED=false | ||
if [[ -f ~/gos_create_server_secrets.sh ]]; then | ||
source ~/gos_create_server_secrets.sh | ||
CURL_OUTPUT="" | ||
FORCE_REPEAT_IF_ALREADY_REPRODUCED=true | ||
fi | ||
|
||
cd $(dirname "$(realpath "$0")") | ||
|
||
|
||
# Need to export these so that envsubst can see them later. | ||
export HETZNER_LOCATION HETZNER_API_TOKEN AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_DEFAULT_REGION AWS_BUCKET_NAME GOS_BUILD_NUMBER GOS_BUILD_DATETIME | ||
export STARTUP_SCRIPT_B64=$(cat "../../scripts/startup_script.sh" | base64 | tr -d '\n') | ||
export DELETE_SERVER_B64=$(cat "../../scripts/delete_server.sh" | base64 | tr -d '\n') | ||
export BUILD_GOS_B64=$(cat "../../scripts/build_gos.sh" | base64 | tr -d '\n') | ||
export DETECT_DEVICE_B64=$(cat "../../scripts/detect_device.sh" | base64 | tr -d '\n') | ||
export COMPARE_GOS_B64=$(cat "../../scripts/compare_gos.sh" | base64 | tr -d '\n') | ||
export NONROOT_USER="strcat" | ||
|
||
if ! aws s3api head-bucket --bucket "$AWS_BUCKET_NAME" --region "$AWS_DEFAULT_REGION" >/dev/null 2>&1; then | ||
if aws s3api create-bucket --bucket "$AWS_BUCKET_NAME" --region "$AWS_DEFAULT_REGION" >/dev/null 2>&1; then | ||
echo "S3 bucket created successfully: ${AWS_BUCKET_NAME}" | ||
else | ||
echo "S3 bucket ${AWS_BUCKET_NAME} does not exist and could not be created. Check your credentials and permissions." | ||
fi | ||
else | ||
echo "Good! S3 bucket ${AWS_BUCKET_NAME} exists." | ||
fi | ||
|
||
for PIXEL_CODENAME in $PIXEL_CODENAMES; do | ||
read -r GOS_BUILD_NUMBER GOS_BUILD_DATETIME BUILD_ID _ < <(echo $(curl -sL "https://releases.grapheneos.org/${PIXEL_CODENAME}-alpha")) | ||
response_status_code=$(curl -sLI -w "%{http_code}" -o /dev/null "https://${AWS_BUCKET_NAME}.s3.${AWS_DEFAULT_REGION}.amazonaws.com/${PIXEL_CODENAME}-${GOS_BUILD_NUMBER}.html") | ||
|
||
if [[ "$response_status_code" -ge 400 && "$response_status_code" -lt 500 || $FORCE_REPEAT_IF_ALREADY_REPRODUCED == true ]]; then | ||
export PIXEL_CODENAME GOS_BUILD_NUMBER GOS_BUILD_DATETIME | ||
USER_DATA=$(envsubst < cloud-config.tpl.yaml | awk '{printf "%s\\n", $0}') | ||
|
||
SERVER_ID=$(curl -sL -H "Authorization: Bearer $HETZNER_API_TOKEN" --url "https://api.hetzner.cloud/v1/servers" | jq ".servers[] | select(.labels.pixel_codename == \"${PIXEL_CODENAME}\" and .labels.gos_build_number == \"${GOS_BUILD_NUMBER}\") | .id") | ||
[[ ! -z "${SERVER_ID}" ]] && echo "A machine for ${PIXEL_CODENAME}-${GOS_BUILD_NUMBER} already exists!" && continue | ||
|
||
curl -sL ${CURL_OUTPUT} \ | ||
-X POST \ | ||
-H "Authorization: Bearer $HETZNER_API_TOKEN" \ | ||
-H "Content-Type: application/json" \ | ||
-d "{\"user_data\":\"$USER_DATA\",\"image\":\"debian-12\",\"location\":\"${HETZNER_LOCATION}\",\"labels\":{\"pixel_codename\":\"$PIXEL_CODENAME\",\"gos_build_number\":\"$GOS_BUILD_NUMBER\"},\"name\":\"m-$(date +%s)\",\"public_net\":{\"enable_ipv4\":true,\"enable_ipv6\":true},\"server_type\":\"cpx51\",\"start_after_create\":true}" \ | ||
"https://api.hetzner.cloud/v1/servers" | ||
echo "Created machine for ${PIXEL_CODENAME}-${GOS_BUILD_NUMBER}!" | ||
else | ||
echo "Already reproduced ${PIXEL_CODENAME}-${GOS_BUILD_NUMBER}!" | ||
fi | ||
done; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Placeholder. This file will describe a Docker image to be built and provide the build and comparison environment. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Placeholder. This file will describe Docker containers to be run in order to build and reproduce GrapheneOS. It will read environment variables from a .env file from this same directory here. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
#!/bin/bash | ||
set -eo pipefail | ||
|
||
# Build and install payload-dumper-go. Already preparing this right here so it fails early in case it fails. | ||
git clone https://github.com/ssut/payload-dumper-go ~/payload-dumper-go | ||
cd ~/payload-dumper-go | ||
go build | ||
|
||
# Download official builds to ~/comparing/official. Already preparing this right here so it fails early in case it fails. | ||
rm -rf ~/comparing && mkdir -p ~/comparing/official ~/comparing/reproduced ~/comparing/operation_outputs | ||
wget -P ~/comparing/official/ https://releases.grapheneos.org/${PIXEL_CODENAME}-install-${GOS_BUILD_NUMBER}.zip | ||
wget -P ~/comparing/official/ https://releases.grapheneos.org/${PIXEL_CODENAME}-ota_update-${GOS_BUILD_NUMBER}.zip | ||
|
||
# Initial builder preparation. | ||
export OFFICIAL_BUILD=true | ||
source /usr/local/bin/detect_device | ||
mkdir -pv ~/.ssh | ||
curl -sL https://grapheneos.org/allowed_signers > ~/.ssh/grapheneos_allowed_signers | ||
git config --global user.name "grapheneos" | ||
git config --global user.email "grapheneos-build@localhost" | ||
git config --global color.ui false | ||
|
||
# Fetch OS source code tree. | ||
echo "[INFO] Fetching OS tree..." | ||
mkdir -p ~/grapheneos/grapheneos-${GOS_BUILD_NUMBER} | ||
cd ~/grapheneos/grapheneos-${GOS_BUILD_NUMBER} | ||
repo init --depth=1 -u https://github.com/GrapheneOS/platform_manifest.git -b refs/tags/${GOS_BUILD_NUMBER} | ||
cd .repo/manifests | ||
git config gpg.ssh.allowedSignersFile ~/.ssh/grapheneos_allowed_signers | ||
git verify-tag $(git describe) | ||
cd ../.. | ||
repo sync --fail-fast --force-sync --no-clone-bundle --no-tags | ||
|
||
# Build kernel. | ||
echo "[INFO] Building kernel..." | ||
mkdir -p ~/android/kernel/${PIXEL_GENERATION_CODENAME} | ||
cd ~/android/kernel/${PIXEL_GENERATION_CODENAME} | ||
repo init --depth=1 -u https://github.com/GrapheneOS/kernel_manifest-${PIXEL_GENERATION_SOC_CODENAME}.git -b refs/tags/${GOS_BUILD_NUMBER} | ||
repo sync --fail-fast --force-sync --no-clone-bundle --no-tags | ||
${KERNEL_BUILD_COMMAND} | ||
cd ~/grapheneos/grapheneos-${GOS_BUILD_NUMBER} | ||
REAL_KERNEL_PREBUILTS_PATH=$(realpath device/google/${PIXEL_GENERATION_CODENAME}-kernels/*/grapheneos/) | ||
find ${REAL_KERNEL_PREBUILTS_PATH}/ -type f ! -path '*kernel-headers/*' -delete | ||
mv ~/android/kernel/${PIXEL_GENERATION_CODENAME}/out/${PIXEL_GENERATION_CODENAME}/dist/* ${REAL_KERNEL_PREBUILTS_PATH} | ||
rm -rf ~/android/kernel/${PIXEL_GENERATION_CODENAME} | ||
|
||
# Build kernel for microdroid. | ||
echo "[INFO] Building kernel for microdroid pVMs..." | ||
mkdir -p ~/android/kernel/6.6 | ||
cd ~/android/kernel/6.6 | ||
repo init --depth=1 -u https://github.com/GrapheneOS/kernel_manifest-6.6.git -b refs/tags/${GOS_BUILD_NUMBER} | ||
repo sync --fail-fast --force-sync --no-clone-bundle --no-tags | ||
tools/bazel run //common:kernel_aarch64_microdroid_dist --config=stamp --lto=full | ||
cd ~/grapheneos/grapheneos-${GOS_BUILD_NUMBER} | ||
mv ~/android/kernel/6.6/out/kernel_aarch64_microdroid/dist/Image packages/modules/Virtualization/guest/kernel/android15-6.6/arm64/kernel-6.6 | ||
mv ~/android/kernel/6.6/out/kernel_aarch64_microdroid/dist/* packages/modules/Virtualization/guest/kernel/android15-6.6/arm64/ | ||
rm -rf ~/android/kernel/6.6 | ||
|
||
# Prepare adevtool to fetch vendor blobs. | ||
echo "[INFO] Preparing adevtool..." | ||
yarnpkg install --cwd vendor/adevtool/ | ||
source build/envsetup.sh | ||
lunch sdk_phone64_x86_64-cur-user | ||
m aapt2 lpunpack deapexer | ||
echo "[INFO] Downloading and placing vendor blobs..." | ||
PATH=$PATH:/sbin:/usr/sbin:/usr/local/sbin vendor/adevtool/bin/run generate-all -d ${PIXEL_CODENAME} | ||
|
||
# Set up build environment for building the base OS and then build it. | ||
echo "[INFO] Building OS..." | ||
source build/envsetup.sh | ||
export BUILD_DATETIME="$GOS_BUILD_DATETIME" | ||
export BUILD_NUMBER="$GOS_BUILD_NUMBER" | ||
lunch ${PIXEL_CODENAME}-cur-user | ||
m ${M_BUILD_PARAMS} | ||
|
||
# Generate keys. Note that these keys are irrelevant because this build will not be used anywhere. | ||
rm -rf keys && mkdir -p keys/${PIXEL_CODENAME} | ||
cd keys/${PIXEL_CODENAME} | ||
CN=$(head /dev/urandom | tr -dc A-Za-z | head -c 8) | ||
export password=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 10) | ||
echo ${password} | ../../development/tools/make_key releasekey "/CN=${CN}/" || : | ||
echo ${password} | ../../development/tools/make_key platform "/CN=${CN}/" || : | ||
echo ${password} | ../../development/tools/make_key shared "/CN=${CN}/" || : | ||
echo ${password} | ../../development/tools/make_key media "/CN=${CN}/" || : | ||
echo ${password} | ../../development/tools/make_key networkstack "/CN=${CN}/" || : | ||
echo ${password} | ../../development/tools/make_key sdk_sandbox "/CN=${CN}/" || : | ||
echo ${password} | ../../development/tools/make_key bluetooth "/CN=${CN}/" || : | ||
openssl genrsa 4096 | openssl pkcs8 -topk8 -scrypt -passout pass:${password} -out avb.pem | ||
sed -i "s/\['openssl', 'rsa',/\['openssl', 'rsa', '-passin', 'pass:${password}',/" ../../external/avb/avbtool.py # Make it prompt no password | ||
../../external/avb/avbtool.py extract_public_key --key avb.pem --output avb_pkmd.bin | ||
cd ../.. | ||
ssh-keygen -t ed25519 -f keys/${PIXEL_CODENAME}/id_ed25519 -N "" | ||
|
||
# Prepare ZIP packages. | ||
. script/finalize.sh | ||
script/generate-release.sh ${PIXEL_CODENAME} ${BUILD_NUMBER} | ||
|
||
# Done. | ||
echo "[INFO] Finished building!" |
Oops, something went wrong.