Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
name: PR Check

on:
pull_request_target:
pull_request:
branches: [main]

permissions:
contents: read
packages: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand Down Expand Up @@ -36,7 +35,4 @@ jobs:
uses: gradle/actions/setup-gradle@v4

- name: Run check
env:
GH_PACKAGES_USER: ${{ secrets.GH_CI_USER }}
GH_PACKAGES_TOKEN: ${{ secrets.GH_CI_TOKEN }}
run: ./gradlew check --stacktrace
12 changes: 0 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,6 @@ We're hoping to have an update on that front later this month. In the meantime,
are [right here](docs/system_app).

## Quickstart
### Grabbing a token
We're currently hosting our library builds with GitHub Packages so each artifact can live beside its source. The tradeoff is that you'll need to add a GitHub token with package read access to your local build environment. **We are considering migrating to Maven Central to avoid this requirement when everything goes public.**
For now, you can either add environment variables with your username and token:
```
GITHUB_ACTOR=your_username
GITHUB_TOKEN=your_token
```
or you can add them to your `local.properties` file:
```
gpr.user=your_username
gpr.key=your_token
```

### Running your Tool
**You can test your tool on any Android device or emulator**, but certain functionality (receiving push notifications, requesting special permissions) can only be tested with:
Expand Down
36 changes: 2 additions & 34 deletions builder/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,8 @@
# docker build \
# --build-arg SDK_GIT_URL=https://github.com/lightphone/light-sdk \
# --build-arg SDK_GIT_REF=<commit-sha> \
# --secret id=github_token,env=GH_PACKAGES_TOKEN \
# --secret id=gh_packages_user,env=GH_PACKAGES_USER \
# -t lightphone/light-builder:<tag> builder/
#
# `github_token` is a single GitHub PAT used for two things:
# 1. cloning the SDK repo if it's private (stage 2)
# 2. authenticating to GitHub Packages while pre-warming gradle (stage 3)
# Needs `repo` and `read:packages` scopes. Public SDK + no GH Packages deps
# means you can omit both secrets entirely.
#
# Runtime:
# docker run --rm --network=none \
# -v /tmp/out:/out lightphone/light-builder:<tag> \
Expand Down Expand Up @@ -90,38 +82,16 @@ ARG SDK_GIT_REF
# SDK_GIT_REF must be a commit-ish; fail loud if the caller forgot it.
RUN test -n "${SDK_GIT_REF}" || (echo "SDK_GIT_REF build arg required" >&2; exit 1)

# If a github_token secret is mounted, use it to clone (works for private repos).
# Otherwise fall back to an anonymous clone (public repos). The token is read
# from the secret mount and used via a one-shot credential helper, so it never
# lands in process args, git config, or any image layer.
RUN --mount=type=secret,id=github_token,uid=0,mode=0400,target=/run/secrets/gh \
set -eu; \
if [ -s /run/secrets/gh ]; then \
echo ">> github_token secret received ($(wc -c < /run/secrets/gh) bytes)"; \
TOKEN="$(cat /run/secrets/gh)"; \
git -c credential.helper="!f() { echo username=x-access-token; echo password=$TOKEN; }; f" \
clone --no-tags "${SDK_GIT_URL}" /opt/light-sdk; \
else \
echo ">> no github_token secret mounted; attempting anonymous clone"; \
git clone --no-tags "${SDK_GIT_URL}" /opt/light-sdk; \
fi; \
RUN git clone --no-tags "${SDK_GIT_URL}" /opt/light-sdk; \
git -C /opt/light-sdk checkout -q "${SDK_GIT_REF}"; \
rm -rf /opt/light-sdk/.git

# ─── Stage 3: pre-warm gradle cache ──────────────────────────────────────────
# This is the only stage that needs network for Maven repos + GH Packages.
# Once it's done, GRADLE_USER_HOME holds every dep needed for any tool/
# build of this SDK commit, and runtime can run --offline.
FROM --platform=linux/amd64 sdk AS warm

RUN --mount=type=secret,id=github_token,uid=0,mode=0400,target=/run/secrets/gh_token \
--mount=type=secret,id=gh_packages_user,uid=0,mode=0400,target=/run/secrets/gh_user \
set -eux; \
set +x; \
export GH_PACKAGES_TOKEN="$(cat /run/secrets/gh_token 2>/dev/null || true)"; \
export GH_PACKAGES_USER="$(cat /run/secrets/gh_user 2>/dev/null || true)"; \
set -x; \
cd /opt/light-sdk; \
RUN cd /opt/light-sdk; \
./gradlew :tool:assembleRelease --no-daemon --stacktrace; \
# Drop build outputs but keep the populated dep cache.
find /opt/light-sdk -path '*/build' -type d -prune -exec rm -rf {} +
Expand All @@ -141,8 +111,6 @@ COPY bin /opt/light-builder/bin

RUN chmod +x /opt/light-builder/bin/build-apk.sh

# Non-root user for the build. The user cannot read GH_PACKAGES_TOKEN or any
# other build-time secret because secret mounts are unmounted between stages.
RUN useradd --create-home --shell /bin/bash builder \
&& chown -R builder:builder /opt/light-sdk /opt/gradle-cache /opt/light-builder

Expand Down
19 changes: 1 addition & 18 deletions builder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,30 +76,14 @@ flag and AGP signs with the shared dev keystore as usual.
## Building the image

```sh
GH_PACKAGES_USER=<your-gh-user> \
GH_PACKAGES_TOKEN=<PAT with repo + read:packages scopes> \
DOCKER_BUILDKIT=1 docker build \
-f builder/Dockerfile \
--build-arg SDK_GIT_URL=https://github.com/lightphone/light-sdk \
--build-arg SDK_GIT_REF=<commit-sha-or-tag> \
--secret id=github_token,env=GH_PACKAGES_TOKEN \
--secret id=gh_packages_user,env=GH_PACKAGES_USER \
-t lightphone/light-builder:<tag> \
builder/
```

A single GitHub PAT does two jobs at image-build time:

1. **Stage 2** clones the (currently private) SDK repo — needs `repo` scope.
2. **Stage 3** pre-warms the gradle dependency cache, which includes
`com.thelightphone.lp3keyboard` from GitHub Packages — needs `read:packages`.

Both stages read the same token via BuildKit secret mounts (`id=github_token`
for the clone, `id=github_token` + `id=gh_packages_user` for the warm-up).
The secrets are mounted only for the steps that need them and never end up
in any image layer. When both the SDK repo and the Packages deps become
public, we will omit the secrets entirely.

### Apple Silicon

The Dockerfile pins `--platform=linux/amd64` on every stage because Google
Expand Down Expand Up @@ -197,8 +181,7 @@ commit.
- **Full bit-reproducibility.** AGP, R8, ZIP packaging, and signed-block
layout each introduce non-determinism. The extraction-and-build pipeline here is deterministic,
but the gradle output is only "reproducible enough that diffs are inspectable".
- **Everything should be public.** Eventually, we won't require any GitHub creds here. The SDK will be public,
tools will only be buildable if they are public, and we'll (likely) host our build artifacts on Maven Central.
- **Tools should be public.** Eventually, tools will only be buildable if they are public.

## Tests

Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
light-keyboard = { module = "com.thelightphone.lp3keyboard:ui", version = "0.0.16"}
light-keyboard = { module = "com.github.lightphone:light-keyboard", version = "v0.0.16"}
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "camerax" }
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camerax" }
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camerax" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class LightSdkPlugin : Plugin<Project> {
"org.unifiedpush.android:connector",
"androidx.core:core-splashscreen",
"com.thelightphone.lp3keyboard",
"com.github.lightphone:light-keyboard",
"androidx.room",
"androidx.work",
"androidx.startup",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ sealed interface LightServiceMethod<TRequest, TResponse> {
}
}

// TODO we're gonna forget to add manually, maybe use reflection?
val allMethods: Map<String, LightServiceMethod<*, *>> = listOf(
LightServiceMethod.GetToken,
LightServiceMethod.GetVersion,
Expand Down
18 changes: 2 additions & 16 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import java.util.Properties

pluginManagement {
repositories {
google()
Expand All @@ -8,25 +6,13 @@ pluginManagement {
}
}

val localProperties = Properties()
val localPropertiesFile = file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.inputStream().use { localProperties.load(it) }
}
val ghUsername = localProperties.getProperty("gpr.user") ?: System.getenv("GH_PACKAGES_USER")
val ghPassword = localProperties.getProperty("gpr.key") ?: System.getenv("GH_PACKAGES_TOKEN")

dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven {
name = "GitHubPackages-Keyboard"
url = uri("https://maven.pkg.github.com/lightphone/light-keyboard")
credentials {
username = ghUsername
password = ghPassword
}
name = "JitPack"
url = uri("https://jitpack.io")
}
}
}
Expand Down
Loading