-
Notifications
You must be signed in to change notification settings - Fork 1
Keystores
vibe-coder-server can generate and store Android signing keystores per
package name. The UI lives under Settings → Keystores
(/settings/keystores) and writes 4 files per package into the persistent
volume ${VIBE_DATA_ROOT}/dev-tools/keystores/ (host) →
/home/vibe/keystores/ (container).
For an Android applicationId like com.siamakerlab.myapp, creation produces:
| File | Purpose |
|---|---|
com.siamakerlab.myapp.keystore |
Release signing key (PKCS12, RSA 4096, validity 100 years by default) |
com.siamakerlab.myapp-debug.keystore |
Debug signing key (same DN + password, separate file so debug-vs-release fingerprints differ) |
com.siamakerlab.myapp-keystore.properties |
Gradle signing config: storeFile / storePassword / keyAlias / keyPassword
|
com.siamakerlab.myapp-admob.properties |
(Optional) AdMob app ID + ad-unit IDs — written only if at least one ID is provided in the form |
Permissions are set to 600 (files) and 700 (directory) on creation.
Edit server.yml (or its env equivalent) once with the operator's standard
distinguished-name fields:
keystore:
defaults:
name: "Jangwook Lee" # CN
organization: "Sia Makerlab" # O
unit: "Mobile" # OU
country: "KR" # C (2 letters)
state: "Chungbuk" # ST
city: "Jecheon" # L
defaultPassword: "********" # plaintext — env override recommended
validityYears: 100Then every keystore form in the UI is pre-filled and only requires the package name (and optionally AdMob IDs).
The plaintext defaultPassword is convenient but exposes the key — for
production-style deployments set the value via the env var
VIBECODER_KEYSTORE_DEFAULT_PASSWORD (or compose secret file) and leave
the YAML field blank.
- Open
/settings/keystores. - Read the Existing keystores table to confirm what already exists.
- Use the Create keystore form:
- Type the Android
applicationId(must match^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$). - Password (pre-filled from defaults).
- Optionally expand "AdMob IDs" if you want the
-admob.propertiesfile too. - Submit. The 4 files appear immediately.
- Type the Android
-
Back up
${VIBE_DATA_ROOT}/dev-tools/keystores/to a separate disk or cloud storage. Losing the release.keystoremakes Play Store updates to thatapplicationIdpermanently impossible.
You no longer have to leave for Settings → Keystores to sign a project.
When a project has no matching keystore yet, its Builds page
(/projects/{id}/builds, also the Builds tab inside the project tabs view)
shows the "keystore not prepared" warning box with a collapsible
Create this project's keystore form right inside it:
- The package name is locked to the project's
applicationId(read-only). ThePOST /projects/{id}/keystorehandler ignores any submitted package name and usesproject.packageName, so the new keystore set is auto-linked by the usual package-name prefix match — no extra wiring step. - Only the password is required. The distinguished-name fields (CN / O / OU / C / ST / L) and validity are pre-filled.
- On success the page reloads back on the same build page and the Queue build button becomes enabled immediately.
In addition to server.yml keystore.defaults, the last form input is
remembered so the next create form starts from what you typed before:
- After every successful create, the DN meta (name / organization / unit /
country / state / city) and validity years are cached to
/home/vibe/keystores/.last-input.properties. - Passwords, key aliases, and any secret values are never written to this cache — only the non-secret distinguished-name metadata. The password is always entered fresh.
- The cache file is
chmod 600, is not a.keystorefile (so it never shows up in the keystore list), andeffectiveDefaults()overlays it on top of theserver.ymldefaults when rendering any create form.
Each project now has a dedicated Keystore tab (in the project tabs "More"
menu, or /projects/{id}/keystore directly). Unlike the global
Settings → Keystores page, this view is scoped to that one project's
applicationId and is a purpose-built page (not the global page embedded).
It shares the same KeystoreService files, so anything created here is the
same set used for build signing and visible on the global page.
What it offers, each section collapsible (<details>):
-
Status — which of release / debug /
.properties/ admob files exist, and the creation date. -
SHA fingerprints — SHA-1, SHA-256 and MD5 (plus the certificate expiry)
for both the release and debug keystores. These are what you paste into
Firebase, Google Sign-In, Google Maps SDK, etc. They are read lazily by
running
keytool -list -vonly when you expand the section (so the tab preloading the project page never spawns keytool). Click a value to select it for copying. If the store password can't be found in the.propertiesfile, a notice is shown instead. -
AdMob IDs — edit App ID / App-Open / Banner / Native unit IDs and save,
independently of whether a keystore exists. Clearing all four deletes the
-admob.propertiesfile. (Debug builds should still use Google's test ad unit IDs; these stored IDs are intended for release builds.) - Create keystore (shown when none exists) — package locked to the project, DN/validity pre-filled, optional AdMob fields, one password entry.
-
Apply signing to build.gradle.kts — sends the standard Claude-console
prompt that wires
signingConfigsfrom the.propertiesfile (same prompt as the global page's "Apply to project"). - Delete — removes the whole file set for the package.
Routes (all SSR, admin-authenticated + CSRF):
GET /projects/{id}/keystore, GET /projects/{id}/keystore/fingerprints
(HTML fragment), POST /projects/{id}/keystore/{create,admob,apply,delete}.
In app/build.gradle.kts:
import java.io.FileInputStream
import java.util.Properties
val keystorePropertiesFile = file("/home/vibe/keystores/com.siamakerlab.myapp-keystore.properties")
val keystoreProperties = Properties().apply {
if (keystorePropertiesFile.exists()) {
FileInputStream(keystorePropertiesFile).use { load(it) }
}
}
android {
signingConfigs {
create("release") {
if (keystorePropertiesFile.exists()) {
storeFile = file(keystoreProperties["storeFile"]!!)
storePassword = keystoreProperties["storePassword"] as String
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
}
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
}
}
}If your Gradle build runs outside the vibe-coder-server container, mount the keystores volume into the build container (or copy the relevant files to a path Gradle can read).
If the form's "AdMob IDs" block was filled, -admob.properties contains:
admobAppId=ca-app-pub-XXXX~YYYY
appOpenAdUnitId=ca-app-pub-XXXX/YYYY
bannerAdUnitId=ca-app-pub-XXXX/YYYY
nativeAdUnitId=ca-app-pub-XXXX/YYYYLoad it the same way as keystore.properties and apply via
manifestPlaceholders["admobAppId"] + buildConfigField("String", "BANNER_AD_UNIT_ID", "\"${...}\"").
The table next to each keystore has a Delete button. It removes all four files atomically and asks for confirmation. There is no recovery — make sure the host backup is current before deleting anything signed by a release key in production.
A future patch may expose GET /api/keystores + POST /api/keystores so
the Android client can manage keystores from the phone. For now the admin
UI is the only entry point.