security: rotate CVM identity and externalize signing
Some checks are pending
Deploy to GitHub Pages / deploy (push) Waiting to run
Some checks are pending
Deploy to GitHub Pages / deploy (push) Waiting to run
This commit is contained in:
parent
ab90e02f69
commit
3f28c3fdb4
6 changed files with 45 additions and 25 deletions
|
|
@ -2405,7 +2405,7 @@ The committed `package-lock.json` (from commit `f25fc2b`) had entries stripped o
|
||||||
### Android release build
|
### Android release build
|
||||||
- `npm run android:build` → `android/app/build/outputs/apk/release/app-release.apk`.
|
- `npm run android:build` → `android/app/build/outputs/apk/release/app-release.apk`.
|
||||||
- **Versioning**: `package.json` `version` is the single source of truth; `scripts/sync-android-version.mjs` syncs it to `versionName`/`versionCode` (e.g. `1.4.5` → `10405`) during the build. Bump **only** `package.json`.
|
- **Versioning**: `package.json` `version` is the single source of truth; `scripts/sync-android-version.mjs` syncs it to `versionName`/`versionCode` (e.g. `1.4.5` → `10405`) during the build. Bump **only** `package.json`.
|
||||||
- **Signing**: secrets live in the gitignored `keystore.properties`; `scripts/inject-signing-config.mjs` injects a managed block into `android/app/build.gradle` (runs in `android:init`/`android:sync`/`android:build`). The authoritative keystore is **`/home/sovereignsk8/skatespots-release.jks`** (alias `skatespots`). An older repo-root copy had a wrong password and was retired to `~/.backups/keystore/`.
|
- **Signing**: secrets live **outside the repo** at `~/.config/skatespots/android/keystore.properties`; `scripts/inject-signing-config.mjs` injects a managed block into `android/app/build.gradle` (runs in `android:init`/`android:sync`/`android:build`). The authoritative keystore is **`~/.config/skatespots/android/skatespots-release.jks`** (alias `skatespots`, mode 600). An older repo-root copy had a wrong password and was retired to `~/.backups/keystore/`. Never put signing material back in the repo: Vite serves files by filesystem path, not by `.gitignore`.
|
||||||
- `android/` is **generated and gitignored**; recreate with `npm run android:init`.
|
- `android/` is **generated and gitignored**; recreate with `npm run android:init`.
|
||||||
- **Launch splash:** `resources/splash.png` is the orange brand wheel. Capacitor's
|
- **Launch splash:** `resources/splash.png` is the orange brand wheel. Capacitor's
|
||||||
generated drawables used a stock blue placeholder, so `scripts/make-android-splash.mjs`
|
generated drawables used a stock blue placeholder, so `scripts/make-android-splash.mjs`
|
||||||
|
|
@ -3431,7 +3431,7 @@ The community relay moved off the hosted relay.tools instance
|
||||||
- `git push` may print a benign `cannot lock ref ... incorrect old value provided` retry warning; verify with `git fetch origin && git rev-list --left-right --count origin/main...HEAD`.
|
- `git push` may print a benign `cannot lock ref ... incorrect old value provided` retry warning; verify with `git fetch origin && git rev-list --left-right --count origin/main...HEAD`.
|
||||||
|
|
||||||
### Secrets — never commit or paste
|
### Secrets — never commit or paste
|
||||||
`keystore.properties`, `*.jks`, `.env*`, and `android/` are gitignored. Never paste the nsec or keystore password into chat, logs, or command history — keep them in a password manager.
|
Signing material (`keystore.properties`, `*.jks`) lives **outside the repo** at `~/.config/skatespots/android/`; `*.jks`, `.env*`, and `android/` remain gitignored as a safety net. Never paste the nsec or keystore password into chat, logs, or command history — keep them in a password manager. Do not bind the Vite dev server to the LAN; use `adb reverse` for device testing.
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
|
|
|
||||||
19
ZAPSTORE.md
19
ZAPSTORE.md
|
|
@ -156,16 +156,18 @@ Or use Android Studio: right-click `app` → New → Image Asset → import `pub
|
||||||
## Step 5 — Generate a signing keystore (one-time)
|
## Step 5 — Generate a signing keystore (one-time)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
install -d -m 700 ~/.config/skatespots/android
|
||||||
keytool -genkey -v \
|
keytool -genkey -v \
|
||||||
-keystore skatespots-release.jks \
|
-keystore ~/.config/skatespots/android/skatespots-release.jks \
|
||||||
-alias skatespots \
|
-alias skatespots \
|
||||||
-keyalg RSA \
|
-keyalg RSA \
|
||||||
-keysize 2048 \
|
-keysize 2048 \
|
||||||
-validity 10000 \
|
-validity 10000 \
|
||||||
-dname "CN=SkateSpots, OU=SkateSpots, O=SkateSpots, L=Bangkok, S=Bangkok, C=TH"
|
-dname "CN=SkateSpots, OU=SkateSpots, O=SkateSpots, L=Bangkok, S=Bangkok, C=TH"
|
||||||
|
chmod 600 ~/.config/skatespots/android/skatespots-release.jks
|
||||||
```
|
```
|
||||||
|
|
||||||
**Save the keystore file and passwords somewhere safe — you need them for every future release.**
|
Put the passwords in `~/.config/skatespots/android/keystore.properties` (mode 600), not in the repo. **Save the keystore file and passwords somewhere safe — you need them for every future release.**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -175,18 +177,7 @@ keytool -genkey -v \
|
||||||
npm run android:build
|
npm run android:build
|
||||||
```
|
```
|
||||||
|
|
||||||
Or manually with signing:
|
`npm run android:build` injects the external signing config automatically. Do **not** pass signing passwords as `-Pandroid.injected...` command-line properties; that exposes them in shell history and process listings.
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
npx cap sync android
|
|
||||||
cd android
|
|
||||||
./gradlew assembleRelease \
|
|
||||||
-Pandroid.injected.signing.store.file=../skatespots-release.jks \
|
|
||||||
-Pandroid.injected.signing.store.password=YOUR_STORE_PASSWORD \
|
|
||||||
-Pandroid.injected.signing.key.alias=skatespots \
|
|
||||||
-Pandroid.injected.signing.key.password=YOUR_KEY_PASSWORD
|
|
||||||
```
|
|
||||||
|
|
||||||
APK output: `android/app/build/outputs/apk/release/app-release.apk`
|
APK output: `android/app/build/outputs/apk/release/app-release.apk`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,12 @@
|
||||||
* 1. Removes any previously managed signing block.
|
* 1. Removes any previously managed signing block.
|
||||||
* 2. Removes a legacy hardcoded signingConfigs block (inline passwords).
|
* 2. Removes a legacy hardcoded signingConfigs block (inline passwords).
|
||||||
* 3. Writes a managed signingConfigs block into android/app/build.gradle that
|
* 3. Writes a managed signingConfigs block into android/app/build.gradle that
|
||||||
* reads secrets from the gitignored keystore.properties at the repo root.
|
* reads secrets from a keystore.properties file outside the repo.
|
||||||
|
*
|
||||||
|
* The signing material is deliberately outside the Vite project root:
|
||||||
|
* ~/.config/skatespots/android/keystore.properties
|
||||||
|
* ~/.config/skatespots/android/skatespots-release.jks
|
||||||
|
* Override the properties path with SKATESPOTS_KEYSTORE_PROPERTIES.
|
||||||
*
|
*
|
||||||
* It exits quietly (0) when android/ isn't generated yet, or when
|
* It exits quietly (0) when android/ isn't generated yet, or when
|
||||||
* keystore.properties is absent — so debug-only builds without a keystore still
|
* keystore.properties is absent — so debug-only builds without a keystore still
|
||||||
|
|
@ -23,11 +28,16 @@
|
||||||
*/
|
*/
|
||||||
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { dirname, join } from 'node:path';
|
import { dirname, join, resolve } from 'node:path';
|
||||||
|
import { homedir } from 'node:os';
|
||||||
|
|
||||||
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
const buildGradle = join(root, 'android', 'app', 'build.gradle');
|
const buildGradle = join(root, 'android', 'app', 'build.gradle');
|
||||||
const keystoreProps = join(root, 'keystore.properties');
|
const keystoreProps = resolve(
|
||||||
|
process.env.SKATESPOTS_KEYSTORE_PROPERTIES ??
|
||||||
|
join(homedir(), '.config', 'skatespots', 'android', 'keystore.properties'),
|
||||||
|
);
|
||||||
|
const gradleString = (value) => value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
||||||
|
|
||||||
const BEGIN = '// >>> managed by scripts/inject-signing-config.mjs';
|
const BEGIN = '// >>> managed by scripts/inject-signing-config.mjs';
|
||||||
const END = '// <<< managed by scripts/inject-signing-config.mjs <<<';
|
const END = '// <<< managed by scripts/inject-signing-config.mjs <<<';
|
||||||
|
|
@ -43,10 +53,11 @@ if (!existsSync(keystoreProps)) {
|
||||||
|
|
||||||
const managedLines = [
|
const managedLines = [
|
||||||
`${BEGIN} >>>`,
|
`${BEGIN} >>>`,
|
||||||
'// Release signing is read from keystore.properties at the repo root',
|
'// Release signing is read from keystore.properties outside the repo.',
|
||||||
'// (gitignored). Edit that file, not this block.',
|
'// Edit that file, not this block.',
|
||||||
'def _keystoreProps = new Properties()',
|
'def _keystoreProps = new Properties()',
|
||||||
"def _keystorePropsFile = rootProject.file('../keystore.properties')",
|
"def _keystorePropsPath = System.getenv('SKATESPOTS_KEYSTORE_PROPERTIES')",
|
||||||
|
`def _keystorePropsFile = new File(_keystorePropsPath ?: '${gradleString(keystoreProps)}')`,
|
||||||
'if (_keystorePropsFile.exists()) {',
|
'if (_keystorePropsFile.exists()) {',
|
||||||
' _keystorePropsFile.withInputStream { _keystoreProps.load(it) }',
|
' _keystorePropsFile.withInputStream { _keystoreProps.load(it) }',
|
||||||
'}',
|
'}',
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import { useNostr } from '@nostrify/react';
|
||||||
|
|
||||||
/** The SkateSpots CVM server identity (see contextvm.org/s/<pubkey>). */
|
/** The SkateSpots CVM server identity (see contextvm.org/s/<pubkey>). */
|
||||||
export const CVM_SERVER_PUBKEY =
|
export const CVM_SERVER_PUBKEY =
|
||||||
'e3564bb877adb0c99a7e4973ccb22e516d7d9eca0027d0bd62a04174ed670530';
|
'940bf0e4fbc64b6c0f09d48c3264723c524a31b5ad0d1aaec361263731689d95';
|
||||||
|
|
||||||
/** The relay the CVM server announces on. */
|
/** The relay the CVM server announces on. */
|
||||||
export const CVM_RELAY = 'wss://relay.contextvm.org';
|
export const CVM_RELAY = 'wss://relay.contextvm.org';
|
||||||
|
|
|
||||||
|
|
@ -279,7 +279,7 @@ Version: v0.1.0`;
|
||||||
{ label: t('settingsUi.about.creditMapData'), name: 'BTCMap', url: 'https://btcmap.org/', desc: t('settingsUi.about.creditBtcmap') },
|
{ label: t('settingsUi.about.creditMapData'), name: 'BTCMap', url: 'https://btcmap.org/', desc: t('settingsUi.about.creditBtcmap') },
|
||||||
{ label: t('settingsUi.about.creditCrowdfunding'), name: 'Angor', url: 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z', desc: t('settingsUi.about.creditAngor') },
|
{ label: t('settingsUi.about.creditCrowdfunding'), name: 'Angor', url: 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z', desc: t('settingsUi.about.creditAngor') },
|
||||||
{ label: t('settingsUi.about.creditDistributed'), name: 'Zapstore', url: 'https://zapstore.dev/apps/org.skatespots.app', desc: t('settingsUi.about.creditZapstore') },
|
{ label: t('settingsUi.about.creditDistributed'), name: 'Zapstore', url: 'https://zapstore.dev/apps/org.skatespots.app', desc: t('settingsUi.about.creditZapstore') },
|
||||||
{ label: t('settingsUi.about.creditTrust'), name: 'ContextVM', url: 'https://contextvm.org/s/e3564bb877adb0c99a7e4973ccb22e516d7d9eca0027d0bd62a04174ed670530', desc: t('settingsUi.about.creditContextvm') },
|
{ label: t('settingsUi.about.creditTrust'), name: 'ContextVM', url: 'https://contextvm.org/s/940bf0e4fbc64b6c0f09d48c3264723c524a31b5ad0d1aaec361263731689d95', desc: t('settingsUi.about.creditContextvm') },
|
||||||
].map((item) => (
|
].map((item) => (
|
||||||
<a
|
<a
|
||||||
key={item.name}
|
key={item.name}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,26 @@ export default defineConfig(() => ({
|
||||||
// The regular web deploy keeps '/' (served from a domain root).
|
// The regular web deploy keeps '/' (served from a domain root).
|
||||||
base: process.env.CAPACITOR_BUILD ? './' : '/',
|
base: process.env.CAPACITOR_BUILD ? './' : '/',
|
||||||
server: {
|
server: {
|
||||||
host: "::",
|
// Keep the dev server off the LAN. The phone preview uses localhost, and
|
||||||
|
// native testing should use `adb reverse` rather than exposing Vite.
|
||||||
|
host: "127.0.0.1",
|
||||||
port: 8080,
|
port: 8080,
|
||||||
|
fs: {
|
||||||
|
strict: true,
|
||||||
|
// Defence in depth: secrets live outside the repo, but never serve them
|
||||||
|
// even if someone temporarily copies one back in.
|
||||||
|
deny: [
|
||||||
|
'.env',
|
||||||
|
'.env.*',
|
||||||
|
'*.{crt,pem}',
|
||||||
|
'**/.git/**',
|
||||||
|
'**/keystore.properties',
|
||||||
|
'**/*.jks',
|
||||||
|
'**/*.keystore',
|
||||||
|
'**/*.p12',
|
||||||
|
'**/*.key',
|
||||||
|
],
|
||||||
|
},
|
||||||
// Dev proxy for the MintRadar directory (no CORS upstream; see public/_redirects).
|
// Dev proxy for the MintRadar directory (no CORS upstream; see public/_redirects).
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api/mints': {
|
'/api/mints': {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue