Files
mg4/flake.nix
T
nilsandClaude Opus 5.5 51e6f6fd48 Server: rewrite in Go as an API-only service
Same endpoints, request formats, validation rules and InfluxDB layout as
the Next.js server, so the car needs no change: POST /v1/telemetry,
PUT /v1/consumption/daily, PUT /v1/charging-sessions/{id}, GET
/v1/consumption/history/{id}, GET /v1/health. The dashboard, login and
the /v1/history reads are gone; Grafana takes over the dashboards.

- Go 1.26 standard library (net/http routing, log/slog) plus the
  official InfluxDB 2 client. Fails closed at startup on a missing
  InfluxDB setting or an ingest token under 24 characters.
- ABRP forwarder ported: newest fresh sample, rate limited, never
  queued, credentials only in the form body.
- 22 tests: validation, aggregation, forwarding, config, and the
  handlers through a fake store. govulncheck clean (x/net 0.60.0,
  toolchain go1.26.9).
- Image: static binary on distroless, about 11 MB, UID 65532, port
  8080; the build runs vet and the tests.
- Dev shell: Go, gopls and govulncheck instead of Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
2026-10-17 15:17:42 +02:00

134 lines
5.7 KiB
Nix

{
description = "NilsMG4 — Android Automotive build environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs }:
let
systems = [ "x86_64-linux" "aarch64-linux" ];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f (import nixpkgs {
inherit system;
config = {
allowUnfree = true;
android_sdk.accept_license = true;
};
}));
# Must match app/build.gradle (compileSdk 36.1) and EVHardware/lib (compileSdk 36).
buildToolsVersion = "36.0.0";
in
{
devShells = forAllSystems (pkgs:
let
android = pkgs.androidenv.composeAndroidPackages {
platformVersions = [ "36" "36.1" ];
buildToolsVersions = [ buildToolsVersion ];
includeEmulator = false;
includeNDK = false;
includeSources = false;
includeSystemImages = false;
};
sdk = "${android.androidsdk}/libexec/android-sdk";
# gradle/gradle-daemon-jvm.properties pins the daemon to JDK 21.
jdk = pkgs.jdk21;
buildTools = "${sdk}/build-tools/${buildToolsVersion}";
# AGP otherwise downloads an aapt2 binary from Maven, which is dynamically linked
# against a generic FHS and does not run on NixOS. Use the SDK's patched one.
# Gradle is also pointed at the Nix JDK so the daemon toolchain criteria are met
# locally instead of downloading a (non-NixOS) JDK through foojay.
gradleOpts = builtins.concatStringsSep " " [
"-Dorg.gradle.project.android.aapt2FromMavenOverride=${buildTools}/aapt2"
"-Dorg.gradle.java.installations.paths=${jdk.home}"
"-Dorg.gradle.java.installations.auto-download=false"
];
# The MG4 head unit is signed with the public AOSP test platform key, so that is
# the key a system-UID (unstable) build must carry. See tools/README.md and OTA.md.
aospKeys = "https://raw.githubusercontent.com/aosp-mirror/platform_build/android-9.0.0_r1/target/product/security";
platformCertSha256 = "c8a2e9bccf597c2fb6dc66bee293fc13f2fc47ec77bc6b2b0d52c11f51192ab8";
buildCar = pkgs.writeShellApplication {
name = "build-car";
runtimeInputs = [ jdk pkgs.curl pkgs.openssl pkgs.coreutils pkgs.gnugrep pkgs.gawk ];
text = ''
root=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
cd "$root"
if [ ! -f app/build.gradle ]; then
echo "build-car: run this from the NilsMG4 checkout" >&2
exit 1
fi
export ANDROID_HOME=${sdk} ANDROID_SDK_ROOT=${sdk} JAVA_HOME=${jdk.home}
export GRADLE_OPTS="${gradleOpts}"
echo "sdk.dir=$ANDROID_HOME" > local.properties
# Keys live in tools/, which .gitignore keeps out of the repository.
for f in platform.pk8 platform.x509.pem; do
if [ ! -f "tools/$f" ]; then
echo "Fetching AOSP test key tools/$f"
curl -sSfL -o "tools/$f" "${aospKeys}/$f"
fi
done
cert=$(openssl x509 -in tools/platform.x509.pem -outform der | sha256sum | cut -d' ' -f1)
if [ "$cert" != "${platformCertSha256}" ]; then
echo "build-car: tools/platform.x509.pem has SHA-256 $cert, expected ${platformCertSha256}" >&2
exit 1
fi
./gradlew assembleUnstableDebug "$@"
version=$(./gradlew -q printUnstableVersion "$@" | tail -1)
in=app/build/outputs/apk/unstable/debug/NilsMG4-unstable-debug.apk
mkdir -p build/car
aligned=build/car/aligned.apk
out="build/car/NilsMG4-unstable-$version.apk"
"${buildTools}/zipalign" -f -p 4 "$in" "$aligned"
# Options come before the input APK; apksigner rejects the other order.
"${buildTools}/apksigner" sign \
--key tools/platform.pk8 --cert tools/platform.x509.pem \
--out "$out" "$aligned"
rm -f "$aligned" "$out.idsig"
signer=$("${buildTools}/apksigner" verify --print-certs "$out" \
| awk -F': ' '/Signer #1 certificate SHA-256 digest/ { print $2 }')
if [ "$signer" != "${platformCertSha256}" ]; then
echo "build-car: $out is signed with $signer, expected ${platformCertSha256}" >&2
rm -f "$out"
exit 1
fi
sha256sum "$out" | cut -d' ' -f1 > "$out.sha256"
echo
echo "Signed with the AOSP platform key: $out"
echo "Install on the head unit:"
echo " adb push $out /data/local/tmp/update.apk"
echo " adb shell \"pm install -r -f -d -t /data/local/tmp/update.apk\""
'';
};
in
{
default = pkgs.mkShell {
packages = [ jdk android.androidsdk buildCar pkgs.go pkgs.gopls pkgs.govulncheck pkgs.influxdb2-server pkgs.influxdb2-cli ];
ANDROID_HOME = sdk;
ANDROID_SDK_ROOT = sdk;
JAVA_HOME = jdk.home;
GRADLE_OPTS = gradleOpts;
shellHook = ''
echo "sdk.dir=$ANDROID_HOME" > local.properties
echo "NilsMG4 dev shell — JDK $(java -version 2>&1 | head -1 | cut -d'"' -f2), SDK at $ANDROID_HOME"
echo " ./gradlew assembleStableDebug # debug APK"
echo " ./gradlew assembleUnstableDebug # build that can update itself (OTA)"
echo " build-car # unstable build signed for the car"
'';
};
});
};
}