Skip to content

Configuration

This guide covers environment variables and configuration options for RecordPlatform.

Configuration Migration

Since v2.0, sensitive configurations (database credentials, Redis, email SMTP, RabbitMQ) have been migrated to Nacos configuration center. Only Nacos connection details and security keys (JWT_KEY) remain in environment variables. See Nacos Configuration Template for the complete configuration structure.

Environment Variables

Copy the example file and customize:

bash
cp .env.example .env
vim .env

Core Configuration

CategoryVariableDescriptionDefault
NacosNACOS_HOSTNacos serverlocalhost
NACOS_PORTNacos port8848
NACOS_USERNAMENacos usernamerequired, no default
NACOS_PASSWORDNacos passwordrequired, no default
ProfileSPRING_PROFILES_ACTIVESpring profilelocal

Security Configuration

VariableDescriptionRequirement
JWT_KEYJWT signing key + ID encryption derivationMin 32 characters, high entropy
PUBLIC_REGISTRATION_TENANT_IDServer-side tenant for public registrationSet explicitly; request headers do not choose registration tenant
RATE_LIMIT_TRUSTED_PROXY_CIDRSNumeric trusted proxy IPs/CIDRs for public-proof client-IP rate limitingEmpty by default; configure only platform-controlled proxies
BLOCKCHAIN_RPC_TOKENShared token for backend-to-FISCO Dubbo callsRequired on both backend and fisco; no default
RECORD_PLATFORM_UID_SALTSalt for UID obfuscationRecommended 8–16 random chars
RECORD_PLATFORM_CLIENT_KEYClient key for UID obfuscationRecommended 16–32 random chars

RATE_LIMIT_TRUSTED_PROXY_CIDRS is a comma-separated list of at most 64 numeric IPv4/IPv6 addresses or CIDRs and at most 4096 characters in total. Direct deployments and deployments with an unverified proxy topology must leave it empty; the backend then ignores X-Forwarded-For, X-Real-IP, and Forwarded and uses the canonical direct socket peer. Behind a proxy, an empty allowlist safely places all callers in the proxy peer's shared 120/60 bucket and can reject traffic earlier. Hostnames, URLs, ports, zone IDs, empty entries, invalid prefixes, 0.0.0.0/0, and ::/0 fail application startup. This configuration is an immutable startup trust boundary and requires a restart.

When the immediate socket peer matches the allowlist, the backend parses one XFF line of at most 1024 characters and 16 hops from right to left and skips trusted hops; X-Real-IP is considered only when XFF is absent. Duplicate, invalid, overlong, or over-hop headers fall back to the immediate peer. Every controlled proxy must overwrite or safely append socket-derived forwarding data. Spring/container forwarding rewrites remain disabled with server.forward-headers-strategy=none; configuring server.tomcat.remoteip.remote-ip-header or server.tomcat.remoteip.protocol-header fails startup. Do not install a ForwardedHeaderFilter, external Tomcat RemoteIpValve, or equivalent second parser.

Storage Configuration

S3-compatible storage is configured via Nacos. Basic environment variables:

VariableDescription
S3_ENDPOINTS3 endpoint URL
S3_ACCESS_KEYAccess key
S3_SECRET_KEYSecret key
S3_BUCKET_NAMEBucket name

Fault domain configuration is managed through Nacos and supports runtime refresh.

Blockchain Configuration

VariableDescriptionExample
BLOCKCHAIN_ACTIVEActive chain typelocal-fisco, bsn-fisco, bsn-besu
FISCO_PEER_ADDRESSFISCO node address127.0.0.1:20200
FISCO_CHAIN_IDExpected local FISCO chain IDchain0
FISCO_GROUP_IDExpected local FISCO group IDgroup0
BSN_FISCO_CHAIN_IDExpected BSN FISCO chain ID; required with no default when BLOCKCHAIN_ACTIVE=bsn-fiscoProvider-assigned value
BSN_BESU_RPC_URLBSN Besu JSON-RPC endpoint; required when BLOCKCHAIN_ACTIVE=bsn-besuProvider-assigned HTTPS URL
BSN_BESU_CHAIN_IDExpected BSN Besu numeric chain IDProvider-assigned value
BSN_BESU_PRIVATE_KEYLocal Besu signer private keyDeployment secret; never commit a value
BSN_BESU_CONTRACT_STORAGEVerified BSN Besu Storage contract address0x...
BSN_BESU_CONTRACT_SHARINGVerified BSN Besu Sharing contract address0x...
BSN_BESU_NONCE_STATE_DIRECTORYDurable nonce journal and signer ownership-lock directory/var/lib/record-platform/besu-nonce
FISCO_STORAGE_CONTRACTStorage contract address0x...
FISCO_SHARING_CONTRACTSharing contract address0x...
FISCO_{STORAGE,SHARING}_DEPLOYMENT_TXRequired deployment transaction hash; the active-chain receipt must exist and match the configured address/block0x + 64 hex
FISCO_{STORAGE,SHARING}_DEPLOYMENT_BLOCKRequired deployment block number from the same successful receiptNon-negative decimal
FISCO_{STORAGE,SHARING}_DEPLOYMENT_EFFECTIVE_ATRequired actual activation time, configured with transaction/blockUTC YYYY-MM-DDTHH:MM:SSZ
CONTRACT_DEPLOYMENT_RECEIPT_DIRDurable restricted directory for public deployment audit receiptslog/contract-deployments for local development

scripts/contract-deploy.sh requires explicit local chain/group values and compares them with Console getGroupInfo before compilation and every deployment. It fetches each transaction receipt together with getGroupInfo in one Console session, requires explicit FISCO success status 0, and derives the final transaction/address/block fields from that one receipt. All three deployment evidence fields are mandatory for both contracts; an entirely empty legacy triplet now fails startup. The same variable names carry reviewed BSN deployment evidence, where startup revalidates the receipt through the selected BSN FISCO or Besu client (Besu requires explicit status 1). The guarded local script writes both triplets with one shared effective time and first publishes a credential-free structured receipt; production should place that receipt directory outside ephemeral application storage. env-check.sh validates shape only; restart platform-fisco to perform the authoritative active-chain receipt, runtime-code, and identity checks.

BSN Besu raw writes reserve nonces per canonical signer from the node's PENDING count and a durable local high-watermark. BSN_BESU_NONCE_STATE_DIRECTORY has no runtime default: it must support reliable Java/POSIX file locks and atomic replacement, survive process/container restarts, and be shared by every supported process that could use the same (chainId, signer). Startup holds an exclusive signer lock for the JVM lifetime, so a second writer on that shared directory fails closed. The configured signer key is exclusive to this coordinator and must not be used by an external wallet or uncoordinated process. Do not place the directory on ephemeral container storage, delete its state files to clear an incident, or run the same signer active-active on independent hosts. A cold standby may take over only after the old writer is externally fenced and its lock/state volume is available.

SSL Configuration (Production)

VariableDescription
SSL_KEY_STOREKeystore path
SSL_KEY_STORE_PASSWORDKeystore password
REQUIRE_SSLForce HTTPS (true/false)
HTTP_REDIRECT_PORTHTTP redirect port

Service Port Configuration

VariableDescriptionDefault
SERVER_PORTBackend REST API port8000 (recommended for local/dev; prod profile defaults to 8080 if unset)
DUBBO_FISCO_PORTFISCO Dubbo service port8091
DUBBO_STORAGE_PORTStorage Dubbo service port8092
DUBBO_HOSTService registration IP (for Docker)Required for provider services; no runtime default
QOS_BACKEND_PORTBackend QoS management port22330
QOS_FISCO_PORTFISCO QoS management port22331
QOS_STORAGE_PORTStorage QoS management port22332

Note: DUBBO_HOST is critical in Docker environments to ensure services register with accessible IPs rather than Docker bridge network IPs. Set it explicitly in .env or deployment secrets.

Logging Configuration

VariableDescriptionDefault
LOG_LEVELApplication log levelINFO
LOG_PATHLog file output directory/var/log/record-platform

CORS Configuration

VariableDescriptionExample
CORS_ALLOWED_ORIGINSAllowed frontend origins (comma-separated)http://localhost:3000,http://localhost:5173

API Documentation Configuration

VariableDescriptionDefault
KNIFE4J_USERNAMEKnife4j/Swagger UI usernamerequired, no default
KNIFE4J_PASSWORDKnife4j/Swagger UI passwordrequired, no default

APM Configuration (Optional)

SkyWalking integration for distributed tracing:

VariableDescriptionDefault
SW_AGENT_COLLECTOR_BACKEND_SERVICESSkyWalking OAP collectorlocalhost:11800
SW_AGENT_NAMEService name in SkyWalkingrecord-platform
SW_JDBC_TRACE_SQL_PARAMETERSTrace SQL parameterstrue

Profile Configuration

Available profiles: local, dev, prod

bash
# Run with specific profile
java -jar app.jar --spring.profiles.active=prod

Profile Differences

Featurelocaldevprod
Swagger UIEnabledEnabledDisabled
Druid MonitorEnabledEnabledDisabled
Debug LoggingEnabledPartialDisabled
SSL RequiredNoNoYes

Nacos Configuration

Dynamic configurations are managed in Nacos. Template: docs/public/nacos-config-template.yaml

Key Nacos Configs

yaml
# Storage nodes with fault domains
storage:
  # Required: List of active domains
  active-domains:
    - domain-a
    - domain-b

  # Optional: External endpoint (v3.2.0+)
  # Used for generating presigned URLs, solves cross-network (e.g., VPN) access issues
  # Format: https://host[:port] (no trailing slash)
  external-endpoint: https://s3-secondary.example.com

  # Optional: Standby domain (for failover)
  standby-domain: standby

  # Replication configuration (v3.1.0+)
  replication:
    factor: 2                     # Replica count, default = active domain count
    quorum: auto                  # Quorum strategy: auto|majority|all|number

  # Degraded write configuration (v3.1.0+)
  degraded-write:
    enabled: true                 # Allow degraded writes
    min-replicas: 1               # Minimum replicas in degraded mode
    track-for-sync: true          # Track degraded writes for later sync

  virtualNodesPerNode: 150

  # Optional: Domain detailed configuration
  domains:
    - name: domain-a
      minNodes: 1
      acceptsWrites: true
    - name: domain-b
      minNodes: 1
      acceptsWrites: true
    - name: standby
      minNodes: 0
      acceptsWrites: false

  nodes:
    - name: node-a1
      endpoint: http://minio-a:9000
      faultDomain: domain-a
      weight: 100
    - name: node-b1
      endpoint: http://minio-b:9000
      faultDomain: domain-b
      weight: 100

  # Replica consistency repair configuration
  consistency:
    repair:
      enabled: true               # Enable scheduled repair
      cron: "0 */15 * * * ?"      # Every 15 minutes
      batch-size: 100
      lock-timeout-seconds: 600

  # Data rebalancing configuration
  rebalance:
    enabled: true                 # Enable automatic rebalancing
    rate-limit-per-second: 10     # Max objects copied per second
    cleanup-source: false         # Delete source data after rebalance

Note: active-domains is required and validated at startup. For single-domain development mode, configure only one domain.

Quota Governance

Per-user and per-tenant storage quota enforcement:

PropertyDescriptionDefault
quota.enforcement-modeEnforcement modeSHADOW (log only, no rejection)
quota.rollout.strategyRollout strategyTENANT_WHITELIST
quota.rollout.enforce-tenant-whitelistTenant IDs to enforce (comma-separated)(empty = all tenants when global mode is ENFORCE)
quota.rollout.force-shadowForce SHADOW mode for all tenantsfalse

Tip: Start with global SHADOW mode to observe quota usage without rejecting uploads. With global ENFORCE, an empty whitelist means all tenants are enforced; use force-shadow=true or a non-empty whitelist for a controlled rollout.

Signed-Proof Issuer

Signed proof ZIPs use a dedicated Ed25519 key. Issuance is disabled and fail-closed by default, with no fallback to JWT_KEY, the file-envelope master key, or a blockchain RPC token.

Environment variableDescriptionDefault
PROOF_SIGNING_ENABLEDAllow new issuance and historical rebuildfalse
PROOF_SIGNING_KEY_IDStable key identifier using letters, digits, dot, underscore, or hyphen; max 64 charactersempty
PROOF_SIGNING_KEY_VERSIONPositive key version; increment on rotation1
PROOF_SIGNING_KEY_STATUSMust be ACTIVE for new issuanceDISABLED
PROOF_SIGNING_PRIVATE_KEY_PKCS8Base64 or PEM-wrapped Ed25519 PKCS#8 private keyempty
PROOF_SIGNING_PUBLIC_KEY_SPKIMatching Base64 or PEM X.509 SPKI public keyempty

Before enabling issuance, configure a matching PKCS#8/SPKI pair, non-empty key ID, positive version, and ACTIVE status. Configuration errors do not print key material. First issuance atomically registers (keyId, keyVersion) in the global key table. For rotation, retain old public material and provision the new key under a higher version; never reuse the same ID/version for another SPKI. Inject the private key through deployment secret management, never Git, logs, or exception messages.

Scheduled Tasks Configuration

Share Cleanup

Automatically marks expired shares as inactive:

yaml
share:
  cleanup:
    interval: 300000  # Check every 5 minutes (milliseconds)

Uses distributed lock to prevent duplicate execution in multi-instance deployments.

File Cleanup

Cleans up soft-deleted files after retention period:

yaml
file:
  cleanup:
    retention-days: 30      # Days to retain soft-deleted files
    batch-size: 100         # Files processed per batch
    cron: "0 0 3 * * ?"     # Daily at 3:00 AM

File Key Wrapping Configuration

New file data keys are wrapped by the explicit file.key-envelope.active-provider. Historical reads never fall back to the active provider: they route by the persisted provider ID and contract version.

For local development, local contract v1 preserves the historical AES-GCM envelope format. The base profile may reuse JWT_KEY only for local development compatibility:

yaml
file:
  key-envelope:
    active-provider: local
    active-provider-contract-version: 1
    providers:
      local:
        key-id: local-file-key-v1
        historical-key-ids: ${FILE_KEY_ENVELOPE_LOCAL_HISTORICAL_KEY_IDS:}
        master-key: ${FILE_KEY_ENVELOPE_MASTER_KEY:${JWT_KEY:}}

Production has no JWT fallback. If local is explicitly selected, FILE_KEY_ENVELOPE_MASTER_KEY must be an independent value of at least 32 characters. When changing the local key ID, list every still-referenced previous ID in the comma-separated FILE_KEY_ENVELOPE_LOCAL_HISTORICAL_KEY_IDS allowlist until rotation completes. For Vault Transit, provision a derived aes256-gcm96 key and configure:

yaml
file:
  key-envelope:
    active-provider: vault-transit
    active-provider-contract-version: 1
    providers:
      vault-transit:
        address: https://vault.example.com
        token: ${FILE_KEY_ENVELOPE_VAULT_TOKEN}
        namespace: ${FILE_KEY_ENVELOPE_VAULT_NAMESPACE:}
        mount: transit
        key-name: record-platform-file-key
        key-version: 1
        allow-http: false
        connect-timeout: 2s
        request-timeout: 5s

The application token needs only update capability on the selected key's transit/encrypt, transit/decrypt, and transit/rewrap paths. Keep old provider/key versions available until every historical envelope has been rotated. See Key Management Security for context binding, migration, and HSM deployment boundaries, and the Key Rotation Runbook for dry-run, APPLY, recovery, alert, and retirement procedures.

Download Key Delivery

Encrypted download metadata defaults to grant-v1: it returns a short-lived, session-bound grant instead of plaintext initialKey. Redis is required and an outage fails closed. Keep the production defaults short and never enable compatibility without a time-bounded client migration:

yaml
file:
  key-delivery:
    grant-ttl: ${FILE_KEY_DELIVERY_GRANT_TTL:60s}
    retry-window: ${FILE_KEY_DELIVERY_RETRY_WINDOW:10s}
    max-same-session-retries: ${FILE_KEY_DELIVERY_MAX_SAME_SESSION_RETRIES:1}
    legacy-plaintext-enabled: ${FILE_KEY_DELIVERY_LEGACY_PLAINTEXT_ENABLED:false}
    legacy-plaintext-not-after: ${FILE_KEY_DELIVERY_LEGACY_PLAINTEXT_NOT_AFTER:2026-10-01T00:00:00Z}

grant-ttl must be positive and no more than five minutes. retry-window must be shorter than the TTL; zero to three same-session retries are accepted, with one as the default. plaintext-v0 works only when the client explicitly negotiates it, the switch is enabled, and the deadline has not passed. After the deadline it fails closed even if the switch is accidentally enabled. Production proxies and APM/WAF agents must not cache or capture metadata, decrypt-info, grant POST bodies, consume responses, X-Download-Session-ID, grant references, or initialKey. See Key Management Security for the binding, replay, browser-memory, removal, and rollback boundaries.

Runtime Crypto Agility

crypto.agility selects only from the closed built-in catalog of executable capabilities. Naming an unknown suite or provider does not load an algorithm dynamically; startup or the operation fails closed:

yaml
crypto:
  agility:
    production-mode: true
    allow-experimental-writes: false
    signing-provider: local-ed25519
    signing-provider-contract-version: 1
    signed-proof-signature-suite: JWS-EDDSA-ED25519-V1
    signed-proof-suite: RP-SIGNED-PROOF-ZIP-V2
    suite-lifecycle:
      RP-AES256-GCM-CHUNK-CHAIN-V1:
        deprecated-at: 2027-01-01T00:00:00Z
        disabled-at: 2028-01-01T00:00:00Z

The environment variables are CRYPTO_AGILITY_PRODUCTION_MODE, CRYPTO_AGILITY_ALLOW_EXPERIMENTAL_WRITES, CRYPTO_AGILITY_SIGNING_PROVIDER, CRYPTO_AGILITY_SIGNING_PROVIDER_CONTRACT_VERSION, CRYPTO_AGILITY_SIGNED_PROOF_SIGNATURE_SUITE, and CRYPTO_AGILITY_SIGNED_PROOF_SUITE. Production must keep production-mode=true and allow-experimental-writes=false. The current ML-DSA/ML-KEM entries have no executable provider and always reject production writes; enabling the experimental switch does not turn them into implemented capabilities.

Tenant administrators use /api/v1/admin/crypto-agility with an optimistic expectedVersion to govern new writes and inspect sanitized diagnostics. Historical envelopes and proofs always route by their persisted provider/contract/suite identities rather than current defaults. See the Runtime Crypto Agility Runbook for rollout and rollback boundaries.

Frontend Configuration

Frontend environment variables (platform-frontend/.env):

VariableDescription
PUBLIC_API_BASE_URLBackend API URL
PUBLIC_ENVEnvironment name
PUBLIC_TENANT_IDDefault tenant ID

Released under the Apache 2.0 License.