Skip to content

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 ​

LocationWhat goes thereEncrypted?
macOS KeychainDeveloper credentials (PATs, tokens used locally)✅ Yes (login keychain)
Woodpecker SecretsCI/CD secrets injected into pipeline steps✅ Yes
Cloudflare Worker SecretsRuntime secrets for API workers✅ Yes
~/.zshenvKeychain lookup commands only — never raw values—

Developer Credentials (macOS Keychain) ​

Woodpecker CI Personal Access Token ​

FieldValue
Keychain servicewoodpecker-ci-lucidpal
Keychain accountwmehanna
Loaded as$WOODPECKER_TOKEN via ~/.zshenv
Regenerate athttps://ci.lucidfabrics.com/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.lucidfabrics.com → 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 ​

SecretDescriptionRegenerate at
CLOUDFLARE_API_TOKENPages Write + Workers Scripts Write + Workers Routes WriteCloudflare Dashboard → My Profile → API Tokens
CLOUDFLARE_ACCOUNT_IDeab661fba6c6a4d2e11c090abd0a811bNot 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) ​

SecretDescriptionRegenerate at
LICENSING_API_URLDev licensing service base URLInternal — https://api-dev.lucidfabrics.com
LICENSING_API_KEYX-Admin-Key header for licensing-serviceLicensing-service admin — check lucidfabrics secrets
LICENSING_PRODUCT_IDProduct identifier for lucidpal devlucidpal-dev-001

APNs (silent sync push) — dev ​

SecretDescriptionRegenerate at
APNS_KEY_IDAPNs auth key ID (10-char, e.g. ABC123DEFG)Apple Developer → Keys
APNS_TEAM_IDApple Developer Team ID (10-char)Apple Developer → Membership details
APNS_PRIVATE_KEY_BASE64Base64-encoded .p8 private key for APNs JWT signingDownload .p8 from same Keys page, then base64 -i APNs_*.p8
APNS_BUNDLE_IDApp bundle ID — app.lucidpalFixed — 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 environment field stored in device_push_tokens.

lucidpal-api-production (prod — api.lucidpal.app) ​

SecretDescriptionRegenerate at
LICENSING_API_URLhttps://api.lucidfabrics.comInternal
LICENSING_PRODUCT_IDlucidpal-prod-001Internal

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.

SecretDescriptionRegenerate
WOODPECKER_AGENT_SECRETShared secret between server and agentsopenssl rand -hex 32 — update in Compose + macOS agent plist + restart both
WOODPECKER_GITHUB_CLIENTGitHub OAuth App client ID (Ov23liVtWoZtHzqP7PzN)GitHub → Settings → Developer settings → OAuth Apps → LucidPal CI
WOODPECKER_GITHUB_SECRETGitHub OAuth App client secretSame 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.

KeyEnvironmentRegenerate at
sk_test_*Sandbox (dev)Stripe Dashboard → Developers → API keys
sk_live_*ProductionStripe Dashboard → Developers → API keys
Webhook secretPer-endpointStripe Dashboard → Developers → Webhooks

Rotation Checklist ​

When rotating any secret:

  1. Generate new value at the source (links above)
  2. Update in the storage location (Keychain / Woodpecker / Cloudflare)
  3. For MATCH_PASSWORD: re-encrypt the match repo before rotating
  4. For WOODPECKER_AGENT_SECRET: update Compose file + macOS agent plist + restart both
  5. Verify the next pipeline run succeeds before discarding the old value

Internal — not for distribution