Appearance
Secrets & Credentials
Zero-trust policy: no secret value is ever committed to git. All secrets are stored encrypted. This document describes what each secret is, where it lives, and how to regenerate it.
Storage Locations
| Location | What goes there | Encrypted? |
|---|---|---|
| macOS Keychain | Developer credentials (PATs, tokens used locally) | ✅ Yes (login keychain) |
| Woodpecker Secrets | CI/CD secrets injected into pipeline steps | ✅ Yes |
| Cloudflare Worker Secrets | Runtime secrets for API workers | ✅ Yes |
~/.zshenv | Keychain lookup commands only — never raw values | — |
Developer Credentials (macOS Keychain)
Woodpecker CI Personal Access Token
| Field | Value |
|---|---|
| Keychain service | woodpecker-ci-lucidpal |
| Keychain account | wmehanna |
| Loaded as | $WOODPECKER_TOKEN via ~/.zshenv |
| Regenerate at | https://ci.lucidpal.app/user → Personal Access Token |
Store:
bash
security add-generic-password \
-a "wmehanna" \
-s "woodpecker-ci-lucidpal" \
-w "<token>" \
-T ""~/.zshenv entry:
bash
export WOODPECKER_TOKEN=$(security find-generic-password -a "wmehanna" -s "woodpecker-ci-lucidpal" -w 2>/dev/null)Woodpecker CI Secrets
Configure at: https://ci.lucidpal.app → lucid-fabrics/lucidpal-dev → Settings → Secrets
Apple / TestFlight
| Secret | Description | Regenerate at | | ----------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | | APP_STORE_CONNECT_API_KEY_ID | ASC API Key ID (e.g. 9W74KCHBGG) | App Store Connect → Users & Access → Integrations → App Store Connect API | | APP_STORE_CONNECT_API_ISSUER_ID | ASC API Issuer ID (UUID) | Same page as above | | APP_STORE_CONNECT_API_KEY_CONTENT | Base64-encoded .p8 private key | Same page — download .p8, then base64 -i AuthKey_<ID>.p8 | | MATCH_PASSWORD | Encryption password for match cert repo | Stored in team memory — do not rotate without re-encrypting the match repo | | MATCH_GIT_BASIC_AUTHORIZATION | Base64 user:token for match git repo access | echo -n "user:ghp_token" | base64 — regenerate GitHub PAT at https://github.com/settings/tokens |
Cloudflare
| Secret | Description | Regenerate at |
|---|---|---|
CLOUDFLARE_API_TOKEN | Pages Write + Workers Scripts Write + Workers Routes Write | Cloudflare Dashboard → My Profile → API Tokens |
CLOUDFLARE_ACCOUNT_ID | eab661fba6c6a4d2e11c090abd0a811b | Not rotatable — fixed account ID |
Cloudflare Worker Secrets
Set via wrangler secret put <NAME> (dev) or wrangler secret put <NAME> --env production (prod).
lucidpal-api (dev — api-dev.lucidpal.app)
| Secret | Description | Regenerate at |
|---|---|---|
LICENSING_API_URL | Dev licensing service base URL | Internal — https://api-dev.lucidfabrics.com |
LICENSING_API_KEY | X-Admin-Key header for licensing-service | Licensing-service admin — check lucidfabrics secrets |
LICENSING_PRODUCT_ID | Product identifier for lucidpal dev | lucidpal-dev-001 |
APNs (silent sync push) — dev
| Secret | Description | Regenerate at |
|---|---|---|
APNS_KEY_ID | APNs auth key ID (10-char, e.g. ABC123DEFG) | Apple Developer → Keys |
APNS_TEAM_ID | Apple Developer Team ID (10-char) | Apple Developer → Membership details |
APNS_PRIVATE_KEY_BASE64 | Base64-encoded .p8 private key for APNs JWT signing | Download .p8 from same Keys page, then base64 -i APNs_*.p8 |
APNS_BUNDLE_ID | App bundle ID — app.lucidpal | Fixed — matches CFBundleIdentifier in Info.plist |
All four APNs secrets are optional. Omitting them disables silent push without affecting sync functionality. The same key works for dev (sandbox) and prod (production) — endpoint selection is determined by the
environmentfield stored indevice_push_tokens.
lucidpal-api-production (prod — api.lucidpal.app)
| Secret | Description | Regenerate at |
|---|---|---|
LICENSING_API_URL | https://api.lucidfabrics.com | Internal |
LICENSING_PRODUCT_ID | lucidpal-prod-001 | Internal |
APNs (silent sync push) — prod
Same four secrets as dev (APNS_KEY_ID, APNS_TEAM_ID, APNS_PRIVATE_KEY_BASE64, APNS_BUNDLE_ID). Use the same key — the worker routes to api.push.apple.com (production) vs api.sandbox.push.apple.com based on the environment column in device_push_tokens.
Woodpecker Server Config (LXC 120)
Not rotated unless the server is rebuilt. Stored in /opt/woodpecker/docker-compose.yml on pve-mirna LXC 120.
| Secret | Description | Regenerate |
|---|---|---|
WOODPECKER_AGENT_SECRET | Shared secret between server and agents | openssl rand -hex 32 — update in Compose + macOS agent plist + restart both |
WOODPECKER_GITHUB_CLIENT | GitHub OAuth App client ID (Ov23liVtWoZtHzqP7PzN) | GitHub → Settings → Developer settings → OAuth Apps → LucidPal CI |
WOODPECKER_GITHUB_SECRET | GitHub OAuth App client secret | Same page — regenerate client secret |
Stripe (via Licensing-service)
Stripe keys live in the lucidfabrics licensing-service worker secrets, not in this repo. Documented here for reference.
| Key | Environment | Regenerate at |
|---|---|---|
sk_test_* | Sandbox (dev) | Stripe Dashboard → Developers → API keys |
sk_live_* | Production | Stripe Dashboard → Developers → API keys |
| Webhook secret | Per-endpoint | Stripe Dashboard → Developers → Webhooks |
Rotation Checklist
When rotating any secret:
- Generate new value at the source (links above)
- Update in the storage location (Keychain / Woodpecker / Cloudflare)
- For
MATCH_PASSWORD: re-encrypt the match repo before rotating - For
WOODPECKER_AGENT_SECRET: update Compose file + macOS agent plist + restart both - Verify the next pipeline run succeeds before discarding the old value