Skip to content

Keystores

Sia edited this page May 31, 2026 · 4 revisions

Android Keystore Management (v1.5.0+, inline create v1.57.0)

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 (v1.54.2))
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 (v1.57.0)

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.

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