Skip to content

Keystores

Sia edited this page Jun 2, 2026 · 4 revisions

Android Keystore Management

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).

File set per package

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.

Pre-fill defaults (server.yml)

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: 100

Then 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.

Web flow

  1. Open /settings/keystores.
  2. Read the Existing keystores table to confirm what already exists.
  3. 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.properties file too.
    • Submit. The 4 files appear immediately.
  4. Back up ${VIBE_DATA_ROOT}/dev-tools/keystores/ to a separate disk or cloud storage. Losing the release .keystore makes Play Store updates to that applicationId permanently impossible.

Inline create from the project

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). The POST /projects/{id}/keystore handler ignores any submitted package name and uses project.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.

Last-input pre-fill (no secrets stored)

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 .keystore file (so it never shows up in the keystore list), and effectiveDefaults() overlays it on top of the server.yml defaults when rendering any create form.

Project Keystore tab (v1.93.0)

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 -v only 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 .properties file, 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.properties file. (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 signingConfigs from the .properties file (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}.

Android build wiring

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).

AdMob properties file

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/YYYY

Load it the same way as keystore.properties and apply via manifestPlaceholders["admobAppId"] + buildConfigField("String", "BANNER_AD_UNIT_ID", "\"${...}\"").

Delete

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.

REST API (planned — currently SSR only)

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.

Clone this wiki locally