Also un-ignores docs/ directory so project documentation (specs, plans) is tracked in git. Individual research files remain gitignored.
552 lines
14 KiB
Markdown
552 lines
14 KiB
Markdown
# Release Build & Forgejo Publishing — Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Signed APK + AAB release builds, published as Forgejo release with firmware attachments.
|
|
|
|
**Architecture:** A `signing.properties` file (gitignored) provides Gradle with keystore credentials. A `scripts/release.sh` script builds APK and AAB, collects firmware, tags git, and uploads everything to Forgejo via HTTP API.
|
|
|
|
**Tech Stack:** Gradle 8.14, AGP 8.5.2, keytool, curl, Forgejo API v1
|
|
|
|
---
|
|
|
|
### Pre-check: BUILD_NUMBER timestamp
|
|
|
|
`buildConfigField "String", "BUILD_NUMBER", ..."` staat in `defaultConfig` in `build.gradle`, maar `BUILD_NUMBER` wordt nergens in de Kotlin code gebruikt. Geen impact op release build reproduceerbaarheid. Het veld blijft bestaan (geen actie nodig).
|
|
|
|
---
|
|
|
|
### Task 1: Genereer keystore en sla op in Obsidian vault
|
|
|
|
**Files:**
|
|
- Create: `android/keystore/sylda-release.jks` (gitignored, later)
|
|
- Create: Obsidian vault notitie met credentials
|
|
|
|
- [ ] **Step 1: Genereer keystore in workspace**
|
|
|
|
Run vanaf de project root:
|
|
|
|
```bash
|
|
keytool -genkey -v \
|
|
-keystore android/keystore/sylda-release.jks \
|
|
-alias sylda \
|
|
-keyalg RSA \
|
|
-keysize 2048 \
|
|
-validity 10000 \
|
|
-storepass "SyldaRel34se2026!" \
|
|
-keypass "SyldaRel34se2026!" \
|
|
-dname "CN=Eddy Devink, OU=SylDa, O=SylDa, L=Unknown, ST=Unknown, C=NL"
|
|
```
|
|
|
|
Verify:
|
|
|
|
```bash
|
|
keytool -list -v -keystore android/keystore/sylda-release.jks -storepass "SyldaRel34se2026!" | head -10
|
|
```
|
|
|
|
Expected: Alias `sylda`, certificate details.
|
|
|
|
- [ ] **Step 2: Bewaar in Obsidian vault**
|
|
|
|
Maak notitie `Homelab/Project_SylDa/keystore.md` via `obsidian-vault` skill of directe CouchDB API met:
|
|
|
|
```
|
|
# SylDa Release Keystore
|
|
|
|
- Path workspace: android/keystore/sylda-release.jks
|
|
- Alias: sylda
|
|
- Key password: SyldaRel34se2026!
|
|
- Store password: SyldaRel34se2026!
|
|
- Validity: 10000 days (tot ~2053)
|
|
- Keyalg: RSA 2048
|
|
|
|
## Keytool commando (hergeneratie)
|
|
|
|
keytool -genkey -v -keystore android/keystore/sylda-release.jks -alias sylda -keyalg RSA -keysize 2048 -validity 10000 -storepass "SyldaRel34se2026!" -keypass "SyldaRel34se2026!" -dname "CN=Eddy Devink, OU=SylDa, O=SylDa, L=Unknown, ST=Unknown, C=NL"
|
|
```
|
|
|
|
- [ ] **Step 3: Commit (keystore wordt later gitignored in Task 4 — nu nog niet gecommit)**
|
|
|
|
Geen git actie nog. Task 4 voegt gitignore toe.
|
|
|
|
---
|
|
|
|
### Task 2: Maak signing.properties
|
|
|
|
**Files:**
|
|
- Create: `android/signing.properties` (gitignored in Task 4)
|
|
|
|
- [ ] **Step 1: Creëer signing.properties**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cat > android/signing.properties << 'EOF'
|
|
KEYSTORE_PATH=keystore/sylda-release.jks
|
|
KEYSTORE_PASSWORD=SyldaRel34se2026!
|
|
KEY_ALIAS=sylda
|
|
KEY_PASSWORD=SyldaRel34se2026!
|
|
EOF
|
|
```
|
|
|
|
- [ ] **Step 2: Verifieer bestand**
|
|
|
|
```bash
|
|
cat android/signing.properties
|
|
```
|
|
|
|
Expected: 4 properties zoals hierboven.
|
|
|
|
---
|
|
|
|
### Task 3: Update build.gradle met signing config
|
|
|
|
**Files:**
|
|
- Modify: `android/app/build.gradle` (hele bestand overschrijven)
|
|
|
|
- [ ] **Step 1: Voeg signing block toe**
|
|
|
|
Huidig bestand (`android/app/build.gradle`):
|
|
|
|
```groovy
|
|
plugins {
|
|
id 'com.android.application'
|
|
id 'org.jetbrains.kotlin.android'
|
|
}
|
|
|
|
android {
|
|
namespace 'nl.eddydevink.sylda'
|
|
compileSdk 34
|
|
|
|
defaultConfig {
|
|
applicationId "nl.eddydevink.sylda"
|
|
minSdk 26
|
|
targetSdk 34
|
|
versionCode 1
|
|
versionName "1.0"
|
|
buildConfigField "String", "BUILD_NUMBER", "\"${new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(new Date())}\""
|
|
}
|
|
|
|
buildFeatures {
|
|
buildConfig true
|
|
}
|
|
|
|
buildTypes {
|
|
release {
|
|
minifyEnabled false
|
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
}
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility JavaVersion.VERSION_17
|
|
targetCompatibility JavaVersion.VERSION_17
|
|
}
|
|
kotlinOptions {
|
|
jvmTarget = '17'
|
|
}
|
|
}
|
|
|
|
dependencies {
|
|
implementation 'androidx.core:core-ktx:1.12.0'
|
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
|
implementation 'io.github.dkaukov:esp32-flash-lib:1.1.17'
|
|
implementation 'com.eclipsesource.minimal-json:minimal-json:0.9.5'
|
|
implementation 'org.slf4j:slf4j-nop:2.0.16'
|
|
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
|
|
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
|
|
}
|
|
```
|
|
|
|
Vervangen door (signing block toegevoegd na `buildTypes {}`):
|
|
|
|
```groovy
|
|
plugins {
|
|
id 'com.android.application'
|
|
id 'org.jetbrains.kotlin.android'
|
|
}
|
|
|
|
def signingPropsFile = rootProject.file('signing.properties')
|
|
def hasSigningConfig = signingPropsFile.exists()
|
|
|
|
android {
|
|
namespace 'nl.eddydevink.sylda'
|
|
compileSdk 34
|
|
|
|
if (hasSigningConfig) {
|
|
def props = new Properties()
|
|
props.load(new FileInputStream(signingPropsFile))
|
|
|
|
signingConfigs {
|
|
release {
|
|
storeFile rootProject.file(props['KEYSTORE_PATH'])
|
|
storePassword props['KEYSTORE_PASSWORD']
|
|
keyAlias props['KEY_ALIAS']
|
|
keyPassword props['KEY_PASSWORD']
|
|
}
|
|
}
|
|
}
|
|
|
|
defaultConfig {
|
|
applicationId "nl.eddydevink.sylda"
|
|
minSdk 26
|
|
targetSdk 34
|
|
versionCode 1
|
|
versionName "1.0"
|
|
buildConfigField "String", "BUILD_NUMBER", "\"${new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(new Date())}\""
|
|
}
|
|
|
|
buildFeatures {
|
|
buildConfig true
|
|
}
|
|
|
|
buildTypes {
|
|
release {
|
|
if (hasSigningConfig) {
|
|
signingConfig signingConfigs.release
|
|
}
|
|
minifyEnabled false
|
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
}
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility JavaVersion.VERSION_17
|
|
targetCompatibility JavaVersion.VERSION_17
|
|
}
|
|
kotlinOptions {
|
|
jvmTarget = '17'
|
|
}
|
|
}
|
|
|
|
dependencies {
|
|
implementation 'androidx.core:core-ktx:1.12.0'
|
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
|
implementation 'io.github.dkaukov:esp32-flash-lib:1.1.17'
|
|
implementation 'com.eclipsesource.minimal-json:minimal-json:0.9.5'
|
|
implementation 'org.slf4j:slf4j-nop:2.0.16'
|
|
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
|
|
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Sync Gradle**
|
|
|
|
```bash
|
|
./gradlew --no-daemon dependencies > /dev/null 2>&1
|
|
echo $?
|
|
```
|
|
|
|
Expected: `0` (geen Gradle-fouten).
|
|
|
|
- [ ] **Step 3: Commit**
|
|
|
|
```bash
|
|
git add android/app/build.gradle
|
|
git commit -m "feat: add release signing config via signing.properties"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 4: Update .gitignore
|
|
|
|
**Files:**
|
|
- Modify: `.gitignore` (toevoegen van nieuwe ignore-regels)
|
|
|
|
- [ ] **Step 1: Voeg keystore en signing.properties toe aan .gitignore**
|
|
|
|
Toevoegen aan `.gitignore`, onder de Android/Gradle sectie:
|
|
|
|
```
|
|
# Android signing (contains secrets)
|
|
android/keystore/
|
|
android/signing.properties
|
|
```
|
|
|
|
- [ ] **Step 2: Verifieer dat keystore nu gitignored is**
|
|
|
|
```bash
|
|
git status android/keystore/ android/signing.properties
|
|
```
|
|
|
|
Expected: geen output (bestanden worden genegeerd).
|
|
|
|
- [ ] **Step 3: Commit**
|
|
|
|
```bash
|
|
git add .gitignore
|
|
git commit -m "chore: gitignore keystore and signing.properties"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 5: Maak release script
|
|
|
|
**Files:**
|
|
- Create: `scripts/release.sh`
|
|
|
|
- [ ] **Step 1: Creëer scripts/ director**
|
|
|
|
```bash
|
|
mkdir -p scripts
|
|
```
|
|
|
|
- [ ] **Step 2: Schrijf release.sh**
|
|
|
|
```bash
|
|
cat > scripts/release.sh << 'SCRIPT'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
NC='\033[0m'
|
|
|
|
log() { echo -e "${GREEN}[release]${NC} $1"; }
|
|
err() { echo -e "${RED}[release]${NC} $1" >&2; }
|
|
|
|
# ------------------------------------------------------------------ config
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
ANDROID_DIR="$PROJECT_DIR/android"
|
|
APP_BUILD_GRADLE="$ANDROID_DIR/app/build.gradle"
|
|
SIGNING_PROPS="$ANDROID_DIR/signing.properties"
|
|
FORGEJO_URL="https://git.eddydevink.nl/api/v1/repos/eddy/Project_SylDa"
|
|
FIRMWARE_DIR="$ANDROID_DIR/app/src/main/assets/firmware"
|
|
|
|
# lees versionName uit build.gradle
|
|
VERSION=$(grep -oP 'versionName\s+"\K[^"]+' "$APP_BUILD_GRADLE")
|
|
TAG="v$VERSION"
|
|
RELEASE_DIR="$PROJECT_DIR/release/$TAG"
|
|
|
|
log "Release v$VERSION (tag $TAG)"
|
|
|
|
# ------------------------------------------------------------------ checks
|
|
if [ ! -f "$SIGNING_PROPS" ]; then
|
|
err "signing.properties niet gevonden op $SIGNING_PROPS"
|
|
exit 1
|
|
fi
|
|
|
|
# ------------------------------------------------------------------ clean
|
|
log "Clean..."
|
|
cd "$ANDROID_DIR"
|
|
./gradlew clean
|
|
|
|
# ------------------------------------------------------------------ build
|
|
log "Build APK + AAB..."
|
|
./gradlew assembleRelease bundleRelease
|
|
|
|
# ------------------------------------------------------------------ verzamel artefacten
|
|
log "Verzamel artefacten in $RELEASE_DIR"
|
|
mkdir -p "$RELEASE_DIR"
|
|
|
|
cp "$ANDROID_DIR/app/build/outputs/apk/release/app-release.apk" "$RELEASE_DIR/"
|
|
cp "$ANDROID_DIR/app/build/outputs/bundle/release/app-release.aab" "$RELEASE_DIR/"
|
|
cp "$FIRMWARE_DIR/bootloader.bin" "$RELEASE_DIR/"
|
|
cp "$FIRMWARE_DIR/partitions.bin" "$RELEASE_DIR/"
|
|
cp "$FIRMWARE_DIR/boot_app0.bin" "$RELEASE_DIR/"
|
|
cp "$FIRMWARE_DIR/firmware.bin" "$RELEASE_DIR/"
|
|
|
|
# ------------------------------------------------------------------ checksums
|
|
log "Checksums..."
|
|
cd "$RELEASE_DIR"
|
|
sha256sum * > checksums.txt
|
|
cat checksums.txt
|
|
|
|
# ------------------------------------------------------------------ git tag
|
|
cd "$PROJECT_DIR"
|
|
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
|
log "Tag $TAG bestaat al, skip git tag."
|
|
else
|
|
log "Git tag $TAG..."
|
|
git tag "$TAG"
|
|
git push origin "$TAG"
|
|
fi
|
|
|
|
# ------------------------------------------------------------------ Forgejo release
|
|
log "Forgejo release aanmaken..."
|
|
TOKEN_FILE="$HOME/.config/sylda/forgejo-token"
|
|
if [ ! -f "$TOKEN_FILE" ]; then
|
|
err "Forgejo token niet gevonden op $TOKEN_FILE"
|
|
err "Genereer een token in Forgejo: Settings > Applications > Generate Token (scope: write:repository)"
|
|
err "Sla op als: echo 'je-token' > $TOKEN_FILE"
|
|
exit 1
|
|
fi
|
|
TOKEN=$(cat "$TOKEN_FILE")
|
|
|
|
# maak release aan
|
|
RELEASE_RESP=$(curl -s -X POST "$FORGEJO_URL/releases" \
|
|
-H "Authorization: token $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"tag_name\": \"$TAG\", \"name\": \"$TAG\", \"body\": \"SylDa Android app + firmware\", \"draft\": false, \"prerelease\": false}")
|
|
|
|
RELEASE_ID=$(echo "$RELEASE_RESP" | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])' 2>/dev/null || true)
|
|
|
|
if [ -z "$RELEASE_ID" ]; then
|
|
err "Release aanmaken mislukt. Response:"
|
|
echo "$RELEASE_RESP"
|
|
exit 1
|
|
fi
|
|
log "Release ID: $RELEASE_ID"
|
|
|
|
# upload artefacten
|
|
for f in *; do
|
|
log " Upload $f..."
|
|
curl -s -X POST "$FORGEJO_URL/releases/$RELEASE_ID/assets?name=$f" \
|
|
-H "Authorization: token $TOKEN" \
|
|
-H "Content-Type: application/octet-stream" \
|
|
--data-binary "@$f" > /dev/null
|
|
done
|
|
|
|
log "Klaar! Release $TAG: $FORGEJO_URL/releases/tag/$TAG (vervang api/v1/repos door web URL)"
|
|
SCRIPT
|
|
|
|
chmod +x scripts/release.sh
|
|
```
|
|
|
|
- [ ] **Step 3: Verifieer script syntax**
|
|
|
|
```bash
|
|
bash -n scripts/release.sh
|
|
echo $?
|
|
```
|
|
|
|
Expected: `0`.
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add scripts/release.sh
|
|
git commit -m "feat: add release build script"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6: Droogtest build
|
|
|
|
**Files:**
|
|
- Geen wijzigingen — verifieer dat release build werkt
|
|
|
|
- [ ] **Step 1: Controleer dat keystore en signing.properties aanwezig zijn**
|
|
|
|
```bash
|
|
ls -la android/keystore/sylda-release.jks android/signing.properties
|
|
```
|
|
|
|
- [ ] **Step 2: Build release APK + AAB**
|
|
|
|
```bash
|
|
./gradlew assembleRelease bundleRelease
|
|
```
|
|
|
|
Expected: `BUILD SUCCESSFUL`
|
|
|
|
- [ ] **Step 3: Verifieer ondertekening APK**
|
|
|
|
```bash
|
|
jarsigner -verify -verbose -certs android/app/build/outputs/apk/release/app-release.apk 2>&1 | grep -E "CN=|jar verified"
|
|
```
|
|
|
|
Expected melding: `CN=Eddy Devink` en `jar verified`.
|
|
|
|
- [ ] **Step 4: Verifieer ondertekening AAB**
|
|
|
|
```bash
|
|
jarsigner -verify -verbose -certs android/app/build/outputs/bundle/release/app-release.aab 2>&1 | grep -E "CN=|jar verified"
|
|
```
|
|
|
|
Expected melding: `CN=Eddy Devink` en `jar verified`.
|
|
|
|
---
|
|
|
|
### Task 7: Maak Forgejo access token aan
|
|
|
|
**Files:**
|
|
- Create: `~/.config/sylda/forgejo-token` (lokaal configuratiebestand, niet in repo)
|
|
|
|
- [ ] **Step 1: Maak token in Forgejo web UI**
|
|
|
|
Navigeer in browser naar: `https://git.eddydevink.nl/user/settings/applications`
|
|
|
|
- Klik "Generate Token"
|
|
- Token name: `syl-release`
|
|
- Scope: vink `write:repository` aan
|
|
- Klik "Generate"
|
|
|
|
Kopieer de gegenereerde token.
|
|
|
|
- [ ] **Step 2: Sla token op**
|
|
|
|
```bash
|
|
mkdir -p ~/.config/sylda
|
|
echo "<gekopieerde-token>" > ~/.config/sylda/forgejo-token
|
|
chmod 600 ~/.config/sylda/forgejo-token
|
|
```
|
|
|
|
- [ ] **Step 3: Sla token op in Obsidian vault**
|
|
|
|
Maak notitie `Homelab/Project_SylDa/forgejo-token.md` met:
|
|
|
|
```
|
|
# Forgejo API Token — syl-release
|
|
|
|
Token voor release uploads. Scope: write:repository.
|
|
|
|
Gegenereerd op: <datum>
|
|
In: Forgejo web UI → Settings → Applications
|
|
|
|
Token: <token>
|
|
|
|
Bestand: ~/.config/sylda/forgejo-token (chmod 600)
|
|
```
|
|
|
|
- [ ] **Step 4: Verifieer token werkt**
|
|
|
|
```bash
|
|
ssh aiaiaiaiagent@nextcloud 'curl -s http://localhost:3000/api/v1/user -H "Authorization: token '"$(cat ~/.config/sylda/forgejo-token)"'"' | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d.get("login","FAIL"))'
|
|
```
|
|
|
|
Expected: `eddy` (de gebruikersnaam in Forgejo).
|
|
|
|
---
|
|
|
|
### Task 8: Draai volledige release
|
|
|
|
**Files:**
|
|
- Geen wijzigingen — voert het script uit
|
|
|
|
- [ ] **Step 1: Check git status (schoon)**
|
|
|
|
```bash
|
|
git status
|
|
```
|
|
|
|
Expected: `nothing to commit, working tree clean`
|
|
|
|
- [ ] **Step 2: Draai release script**
|
|
|
|
```bash
|
|
./scripts/release.sh
|
|
```
|
|
|
|
- [ ] **Step 3: Verifieer release op Forgejo**
|
|
|
|
```bash
|
|
ssh aiaiaiaiagent@nextcloud 'curl -s http://localhost:3000/api/v1/repos/eddy/Project_SylDa/releases/tags/v1.0' | python3 -c 'import sys,json; r=json.load(sys.stdin); print(f"Tag: {r[\"tag_name\"]}, Assets: {len(r[\"assets\"])}"); [print(f" - {a[\"name\"]} ({a[\"size\"]} bytes)") for a in r["assets"]]'
|
|
```
|
|
|
|
Expected: Tag `v1.0`, 6 assets (APK, AAB, 4 firmware bins, checksums.txt).
|
|
|
|
- [ ] **Step 4: Open browser ter verificatie**
|
|
|
|
Open `https://git.eddydevink.nl/eddy/Project_SylDa/releases` en controleer dat release `v1.0` zichtbaar is met alle attachments.
|
|
|
|
---
|
|
|
|
### Post-release: Cleanup
|
|
|
|
Release artefacten in `release/v1.0/` worden niet gecommit (`.gitignore` sluit `*.apk` en `*.aab` al uit). Na release draaien kan deze directory verwijderd worden:
|
|
|
|
```bash
|
|
rm -rf release/
|
|
```
|