Configuration Guide
Comprehensive guide for configuring the WiLicensor Server environment.
Environment Variables
All configuration is managed via environment variables in .env.local.
Database Configuration
POSTGRES_HOST=your-postgres-host.com
POSTGRES_PORT=5432
POSTGRES_USER=your_username
POSTGRES_PASSWORD=your_password
POSTGRES_DATABASE=your_database_name
POSTGRES_SSL=true # Set to 'false' for local development
Notes:
- PostgreSQL 12+ required
- SSL required for production databases
- Connection pooling configured automatically
Outage Grace Register
SEEN_KEYS_PATH=/data/seen-keys.json
Optional. During a database outage /api/check grants a short grace period, but
only to keys that have already validated successfully at least once. That list
lives in memory and is mirrored to this file.
The default path is inside the app directory, which is rebuilt on every deploy.
The register therefore survives a process restart but not a redeploy. If the
database fails shortly after a deploy, customers are refused until each has
synced once. That fails closed (a lockout, never an unearned licence) and clears
itself within one sync interval.
Point this at a mounted volume to remove the window. On Coolify, add a persistent
volume and set the variable to a path inside it.
Subscription Expiry Enforcement
ENFORCE_SUBSCRIPTION_EXPIRY=false
Off by default, and it should stay off until you have decided what happens to
customers whose subscription already lapsed.
What it does when on: Stripe subscription events stamp the paid-through date
onto that customer's keys, and each successful renewal pushes it further out. A
key with a past endAt stops validating on its own, so a cancellation that never
reached the server costs at most one billing period instead of granting access
forever. The date is only ever extended, never shortened, so an out-of-order
webhook cannot claw back entitlement.
What it does when off: nothing is written. The server logs the date it would
have stamped, so you can see the effect before committing to it.
Before switching it on: every existing subscription key has no endAt at
all. Turning this on does not retroactively expire them (they get a date only
when their next Stripe event arrives), but the companion reconcile job does:
node scripts/reconcileSubscriptions.js # report only, changes nothing
node scripts/reconcileSubscriptions.js --json # same, machine readable
Applying changes needs BOTH --apply and ENFORCE_SUBSCRIPTION_EXPIRY=true.
One without the other reports and exits. Accounts with no Stripe record at all
(comped, manual, legacy) are always left alone for a human to judge.
Session Management
SESSION_SECRET=your_128_character_random_hex_string
Generate secret:
npm run secret
Requirements:
- Must be 128+ characters
- Use cryptographically secure random string
- Never share or commit to version control
Session Settings:
- Storage: PostgreSQL
session_pgtable - Expiration: 30 days
- HTTP-only cookies
- Secure cookies in production
Email Configuration
WiLicensor uses Gmail SMTP for email delivery.
EMAIL_SENDER=your-gmail@gmail.com
EMAIL_PASSWORD=your_app_specific_password
EMAIL_FROM_ALIAS=License Server
EMAIL_RECIPIENT=admin@yourdomain.com
Gmail Setup:
- Enable 2-factor authentication on your Gmail account
- Generate App-Specific Password: https://support.google.com/accounts/answer/185833
- Use the 16-character app password (not your regular Gmail password)
Email Functions:
EMAIL_SENDER: Gmail account for sending emailsEMAIL_PASSWORD: App-specific password (required)EMAIL_FROM_ALIAS: Display name in "From" fieldEMAIL_RECIPIENT: Admin email for notifications
Test Email:
node test.js
Stripe Configuration
USE_STRIPE_TEST_KEY=true # Toggle test/production
# Production keys
STRIPE_SECRET_KEY=sk_live_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxx
# Optional: Test keys
STRIPE_SECRET_KEY_TEST=sk_test_xxxxx
STRIPE_WEBHOOK_SECRET_TEST=whsec_test_xxxxx
Setup:
- Get keys from Stripe Dashboard
- Get webhook secret from Webhooks
- Configure webhook endpoint:
https://yourdomain.com/webhook/stripe
Supported Events:
customer.subscription.created- Create license keyscustomer.subscription.deleted- Deactivate license keys
Testing Webhooks Locally:
# Install Stripe CLI
stripe listen --forward-to localhost:5000/webhook/stripe
# Trigger test events
stripe trigger customer.subscription.created
Application Settings
SETTINGS=1 # Sync period in days
PORT=5000 # Server port (default: 5000)
SETTINGS:
- Controls license sync period
- Used in
/api/checkresponses - Default: 1 day
PORT:
- HTTP server port
- Default: 5000
- Must be available on host
Environment File Management
File Structure
License-Server/
├── .env.local # Active configuration (gitignored)
├── .env.local.prod # Production backup
├── .env.local.dev # Development backup
├── .env example.local # Template
└── .env copy 2.local # Alternative template
Creating Configuration
# Copy template
cp ".env copy.local" .env.local
# Generate session secret
npm run secret
# Edit with your credentials
nano .env.local # or use any text editor
Switching Environments
# Switch to development
mv .env.local .env.local.prod
mv .env.local.dev .env.local
# Switch back to production
mv .env.local .env.local.dev
mv .env.local.prod .env.local
Database Settings
Application Settings Table
The Settings table stores application-wide configuration:
{
id: INTEGER,
name: STRING, // typically 'default'
displayName: STRING,
settings: STRING // JSON stringified
}
Available Settings:
Auto-Add Products
{
"autoAdd": true
}
When enabled, products are automatically created if they don't exist when referenced by webhooks or API calls.
Configure via Admin Dashboard:
- Login to admin dashboard
- Navigate to Settings
- Toggle "Auto Add Products"
- Save
Local Development Configuration
Development Database Setup
Windows (Chocolatey):
choco install postgresql15 --params '/Password:localdev' -y
Create Development Database:
PGPASSWORD=localdev psql -U postgres -c "CREATE DATABASE license_dev;"
Development .env.local:
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=localdev
POSTGRES_DATABASE=license_dev
POSTGRES_SSL=false
# Email (use test account or production for notifications)
EMAIL_SENDER=your-test-gmail@gmail.com
EMAIL_PASSWORD=test_app_password
EMAIL_FROM_ALIAS=License Server Dev
EMAIL_RECIPIENT=dev@localhost
# Stripe (use test keys)
USE_STRIPE_TEST_KEY=true
STRIPE_SECRET_KEY=sk_test_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_test_xxxxx
# Session
SESSION_SECRET=generated_secret_from_npm_run_secret
# Application
SETTINGS=1
PORT=5000
Production Configuration Checklist
Before deploying to production, verify:
- Strong
SESSION_SECRET(128+ characters) - PostgreSQL SSL enabled (
POSTGRES_SSL=true) - Production Stripe keys configured
- Gmail App-Specific Password used
-
EMAIL_RECIPIENTset to admin email - Database backups configured
- All secrets secured (not in version control)
- HTTPS enabled
- Webhook URLs configured in payment platforms
- Email delivery tested (
node test.js)
Configuration Validation
Test Database Connection
# Using environment variables
psql -h $POSTGRES_HOST -U $POSTGRES_USER -d $POSTGRES_DATABASE
# Manual test
psql -h your-host -U your-user -d your-database
Test Email Configuration
node test.js
Expected output:
Email sent: 250 2.0.0 OK ...
Test Stripe Connection
# Install Stripe CLI
stripe listen --forward-to localhost:5000/webhook/stripe
# In another terminal
stripe trigger customer.subscription.created
Security Best Practices
Secrets Management
Never commit:
.env.localfiles- Session secrets
- Database passwords
- API keys
- Webhook secrets
Use secure storage:
- Environment variables in hosting platform
- Secret management services (AWS Secrets Manager, Azure Key Vault)
- Encrypted configuration files (with separate key management)
Password Requirements
SESSION_SECRET:
- Minimum 128 characters
- Cryptographically random
- Unique per environment
Database Passwords:
- Minimum 16 characters
- Mix of uppercase, lowercase, numbers, symbols
- Rotated regularly
Gmail App Passwords:
- Use app-specific passwords only
- One password per application
- Revoke if compromised
Troubleshooting Configuration
"Unable to connect to database"
Check:
- Database is running:
pg_isready -h your-host - Credentials correct in
.env.local - SSL setting matches database requirements
- Firewall allows connections
"Email sending failed"
Check:
- App-specific password used (not regular password)
- 2-factor authentication enabled on Gmail
- Environment variables set correctly
- Test with
node test.js
"Invalid session secret"
Check:
SESSION_SECRETis set- Length is 128+ characters
- No special characters causing parsing issues
- Regenerate with
npm run secret
Related Documentation
- ← Back to README
- API Documentation
- Deployment Guide
- Development Guide
- Troubleshooting Guide
- Technical Details
Last Updated: 2025-12-18