diff --git a/api/lib/config.ts b/api/lib/config.ts index c1ab3c9ff..d98d85a02 100644 --- a/api/lib/config.ts +++ b/api/lib/config.ts @@ -8,7 +8,7 @@ import ConnectionPool from './connection-pool.js'; import { ConnectionWebSocket } from './connection-web.js'; import Cacher from './cacher.js'; import type { Server } from './schema.js'; -import { type InferSelectModel } from 'drizzle-orm'; +import { type InferSelectModel, sql } from 'drizzle-orm'; import Models from './models.js'; import process from 'node:process'; import * as pgtypes from './schema.js'; @@ -146,13 +146,170 @@ export default class Config { } catch (err) { console.log(`ok - no server config found: ${err instanceof Error ? err.message : String(err)}`); - server = await models.Server.generate({ - name: 'Default Server', - url: 'ssl://localhost:8089', - api: 'https://localhost:8443' + // Create server with environment variables if available + const serverData: Record = { + name: process.env.CLOUDTAK_Server_name || 'Default Server', + url: process.env.CLOUDTAK_Server_url || 'ssl://localhost:8089', + api: process.env.CLOUDTAK_Server_api || 'https://localhost:8443' + }; + + if (process.env.CLOUDTAK_Server_webtak) { + serverData.webtak = process.env.CLOUDTAK_Server_webtak; + } + + // Handle auth certificates + if (process.env.CLOUDTAK_Server_auth_p12_secret_arn && process.env.CLOUDTAK_Server_auth_password) { + try { + const secrets = new SecretsManager.SecretsManagerClient({ region: process.env.AWS_REGION }); + const secretValue = await secrets.send(new SecretsManager.GetSecretValueCommand({ + SecretId: process.env.CLOUDTAK_Server_auth_p12_secret_arn + })); + + if (secretValue.SecretBinary) { + const pem = (await import('pem')).default; + const p12Buffer = Buffer.from(secretValue.SecretBinary); + + const certs = await new Promise<{ pemCertificate: string; pemKey: string }>((resolve, reject) => { + pem.readPkcs12(p12Buffer, { p12Password: process.env.CLOUDTAK_Server_auth_password }, (err: Error | null, result: { cert: string; key: string }) => { + if (err) { + reject(err); + } else { + resolve({ pemCertificate: result.cert, pemKey: result.key }); + } + }); + }); + + serverData.auth = { + cert: certs.pemCertificate, + key: certs.pemKey + }; + console.error('ok - Extracted certificate and key from P12 binary secret'); + } + } catch (err) { + console.error(`Error extracting P12 from binary secret: ${err instanceof Error ? err.message : String(err)}`); + } + } else if (process.env.CLOUDTAK_Server_auth_cert && process.env.CLOUDTAK_Server_auth_key) { + serverData.auth = { + cert: process.env.CLOUDTAK_Server_auth_cert, + key: process.env.CLOUDTAK_Server_auth_key + }; + } + + server = await models.Server.generate(serverData); + } + + // Update server with environment variables + console.error(`ok - Initial server state: auth.cert=${!!server.auth?.cert}, auth.key=${!!server.auth?.key}, webtak=${!!server.webtak}`); + console.error(`ok - Environment variables: CLOUDTAK_Server_name=${process.env.CLOUDTAK_Server_name}, CLOUDTAK_Server_url=${process.env.CLOUDTAK_Server_url}, CLOUDTAK_Server_api=${process.env.CLOUDTAK_Server_api}, CLOUDTAK_Server_webtak=${process.env.CLOUDTAK_Server_webtak}`); + console.error(`ok - Auth env vars: CLOUDTAK_Server_auth_cert=${!!process.env.CLOUDTAK_Server_auth_cert}, CLOUDTAK_Server_auth_key=${!!process.env.CLOUDTAK_Server_auth_key}, CLOUDTAK_Server_auth_p12_secret_arn=${!!process.env.CLOUDTAK_Server_auth_p12_secret_arn}`); + console.error(`ok - Admin env vars: CLOUDTAK_ADMIN_USERNAME=${!!process.env.CLOUDTAK_ADMIN_USERNAME}, CLOUDTAK_ADMIN_PASSWORD=${!!process.env.CLOUDTAK_ADMIN_PASSWORD}`); + + // Debug all CLOUDTAK environment variables + const cloudtakEnvs = Object.keys(process.env).filter(key => key.startsWith('CLOUDTAK_')).sort(); + console.error(`ok - All CLOUDTAK env vars: ${cloudtakEnvs.join(', ')}`); + + if (process.env.CLOUDTAK_Server_auth_p12_secret_arn) { + console.error(`ok - P12 secret ARN: ${process.env.CLOUDTAK_Server_auth_p12_secret_arn}`); + } else { + console.error('ok - CLOUDTAK_Server_auth_p12_secret_arn is undefined/empty'); + } + + if (process.env.CLOUDTAK_Server_auth_cert) { + console.error(`ok - Direct cert length: ${process.env.CLOUDTAK_Server_auth_cert.length}`); + } + + if (process.env.CLOUDTAK_Server_auth_key) { + console.error(`ok - Direct key length: ${process.env.CLOUDTAK_Server_auth_key.length}`); + } + + const serverEnvUpdates: Record = {}; + let hasServerUpdates = false; + + if (process.env.CLOUDTAK_Server_name) { + serverEnvUpdates.name = process.env.CLOUDTAK_Server_name; + hasServerUpdates = true; + } + if (process.env.CLOUDTAK_Server_url) { + serverEnvUpdates.url = process.env.CLOUDTAK_Server_url; + hasServerUpdates = true; + } + if (process.env.CLOUDTAK_Server_api) { + serverEnvUpdates.api = process.env.CLOUDTAK_Server_api; + hasServerUpdates = true; + } + if (process.env.CLOUDTAK_Server_webtak) { + serverEnvUpdates.webtak = process.env.CLOUDTAK_Server_webtak; + hasServerUpdates = true; + } + + // Handle auth certificates for existing server + console.error('ok - Updating server configuration from environment variables'); + + if (process.env.CLOUDTAK_Server_auth_p12_secret_arn && process.env.CLOUDTAK_Server_auth_password) { + console.error('ok - Processing P12 certificate from binary secret'); + try { + const secrets = new SecretsManager.SecretsManagerClient({ region: process.env.AWS_REGION }); + const secretValue = await secrets.send(new SecretsManager.GetSecretValueCommand({ + SecretId: process.env.CLOUDTAK_Server_auth_p12_secret_arn + })); + + if (secretValue.SecretBinary) { + const pem = (await import('pem')).default; + const p12Buffer = Buffer.from(secretValue.SecretBinary); + + const certs = await new Promise<{ pemCertificate: string; pemKey: string }>((resolve, reject) => { + pem.readPkcs12(p12Buffer, { p12Password: process.env.CLOUDTAK_Server_auth_password }, (err: Error | null, result: { cert: string; key: string }) => { + if (err) { + reject(err); + } else { + resolve({ pemCertificate: result.cert, pemKey: result.key }); + } + }); + }); + + if (certs.pemCertificate && certs.pemKey) { + serverEnvUpdates.auth = { + ...(server.auth || {}), + cert: certs.pemCertificate, + key: certs.pemKey + }; + hasServerUpdates = true; + console.error('ok - Successfully extracted certificate and key from P12 binary secret'); + } else { + console.error('ok - P12 conversion failed: missing certificate or key'); + } + } else { + console.error('ok - No SecretBinary found in secret'); + } + } catch (err) { + console.error(`ok - Error processing P12 binary secret: ${err instanceof Error ? err.message : String(err)}`); + } + } else if (process.env.CLOUDTAK_Server_auth_cert && process.env.CLOUDTAK_Server_auth_key) { + console.error('ok - Using direct certificate and key from environment variables'); + serverEnvUpdates.auth = { + ...(server.auth || {}), + cert: process.env.CLOUDTAK_Server_auth_cert, + key: process.env.CLOUDTAK_Server_auth_key + }; + hasServerUpdates = true; + console.error(`ok - Direct auth configured: cert=${process.env.CLOUDTAK_Server_auth_cert.length} chars, key=${process.env.CLOUDTAK_Server_auth_key.length} chars`); + } else { + console.error('ok - No certificate environment variables found - server will run without client certificates'); + } + + if (hasServerUpdates) { + console.error(`ok - Updates to apply: ${JSON.stringify(Object.keys(serverEnvUpdates))}`); + server = await models.Server.commit(server.id, { + ...serverEnvUpdates, + updated: sql`Now()` }); + console.error(`ok - Server updated: auth.cert=${!!server.auth?.cert}, auth.key=${!!server.auth?.key}, webtak=${!!server.webtak}`); + } else { + console.error('ok - No server updates needed from environment variables'); } + console.error(`ok - Final server state before Config creation: auth.cert=${!!server.auth?.cert}, auth.key=${!!server.auth?.key}, webtak=${!!server.webtak}`); + const config = new Config({ silent: (args.silent || false), noevents: (args.noevents || false), @@ -163,8 +320,10 @@ export default class Config { server, SigningSecret, MediaSecret, API_URL, DynamoDB, Bucket, pg, models, PMTILES_URL }); + console.error(`ok - Config created with server: auth.cert=${!!config.server.auth?.cert}, auth.key=${!!config.server.auth?.key}, webtak=${!!config.server.webtak}`); + if (!config.silent) { - console.error('ok - set env AWS_REGION: us-east-1'); + console.error(`ok - set env AWS_REGION: ${process.env.AWS_REGION}`); console.log(`ok - PMTiles: ${config.PMTILES_URL}`); console.error(`ok - StackName: ${config.StackName}`); } @@ -177,6 +336,24 @@ export default class Config { if (process.env.SubnetPublicB) config.SubnetPublicB = process.env.SubnetPublicB; if (process.env.MediaSecurityGroup) config.MediaSecurityGroup = process.env.MediaSecurityGroup; + // Ensure admin user has admin permissions if credentials provided + if (process.env.CLOUDTAK_ADMIN_USERNAME && process.env.CLOUDTAK_ADMIN_PASSWORD) { + try { + console.error('ok - Ensuring admin user has admin permissions'); + + // Create admin user directly in database with admin permissions + await config.models.Profile.generate({ + username: process.env.CLOUDTAK_ADMIN_USERNAME, + auth: { password: process.env.CLOUDTAK_ADMIN_PASSWORD }, + system_admin: true + }, { upsert: GenerateUpsert.UPDATE }); + + console.error('ok - Admin user ensured with admin permissions'); + } catch (err) { + console.error(`Error ensuring admin user: ${err instanceof Error ? err.message : String(err)}`); + } + } + for (const envkey in process.env) { if (!envkey.startsWith('CLOUDTAK')) continue; diff --git a/api/package-lock.json b/api/package-lock.json index c59e87736..9cd6a8970 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -1,12 +1,12 @@ { "name": "@tak-ps/CloudTAK.api", - "version": "10.22.1", + "version": "10.25.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@tak-ps/CloudTAK.api", - "version": "10.22.1", + "version": "10.25.0", "license": "ISC", "dependencies": { "@aws-sdk/client-batch": "^3.301.0", diff --git a/api/test/config.srv.test.ts b/api/test/config.srv.test.ts index 5c9e68cfd..77b32a102 100644 --- a/api/test/config.srv.test.ts +++ b/api/test/config.srv.test.ts @@ -1,5 +1,6 @@ import test from 'tape'; import Flight from './flight.js'; +import sinon from 'sinon'; const flight = new Flight(); @@ -168,4 +169,133 @@ test('GET api/config/map', async (t) => { t.end(); }); +// Server Environment Variable Integration Tests +test('Server Env: CLOUDTAK_Server_name updates database', async (t) => { + const originalEnv = process.env.CLOUDTAK_Server_name; + process.env.CLOUDTAK_Server_name = 'Env Test Server'; + + try { + const initialServer = await flight.config.models.Server.from(1); + + // Simulate server env update + const updatedServer = await flight.config.models.Server.commit(initialServer.id, { + name: process.env.CLOUDTAK_Server_name + }); + + t.equal(updatedServer.name, 'Env Test Server'); + } catch (err) { + t.error(err, 'no error'); + } + + process.env.CLOUDTAK_Server_name = originalEnv; + t.end(); +}); + +test('Server Env: CLOUDTAK_Server_url updates database', async (t) => { + const originalEnv = process.env.CLOUDTAK_Server_url; + process.env.CLOUDTAK_Server_url = 'ssl://env.test.com:8089'; + + try { + const initialServer = await flight.config.models.Server.from(1); + + const updatedServer = await flight.config.models.Server.commit(initialServer.id, { + url: process.env.CLOUDTAK_Server_url + }); + + t.equal(updatedServer.url, 'ssl://env.test.com:8089'); + } catch (err) { + t.error(err, 'no error'); + } + + process.env.CLOUDTAK_Server_url = originalEnv; + t.end(); +}); + +test('Server Env: auth object updates database', async (t) => { + const originalCert = process.env.CLOUDTAK_Server_auth_cert; + const originalKey = process.env.CLOUDTAK_Server_auth_key; + + process.env.CLOUDTAK_Server_auth_cert = 'test-cert-data'; + process.env.CLOUDTAK_Server_auth_key = 'test-key-data'; + + try { + const initialServer = await flight.config.models.Server.from(1); + + const updatedServer = await flight.config.models.Server.commit(initialServer.id, { + auth: { + cert: process.env.CLOUDTAK_Server_auth_cert, + key: process.env.CLOUDTAK_Server_auth_key + } + }); + + t.deepEqual(updatedServer.auth, { + cert: 'test-cert-data', + key: 'test-key-data' + }); + } catch (err) { + t.error(err, 'no error'); + } + + process.env.CLOUDTAK_Server_auth_cert = originalCert; + process.env.CLOUDTAK_Server_auth_key = originalKey; + t.end(); +}); + +test('Server Env: multiple fields update database', async (t) => { + const originalVars = { + name: process.env.CLOUDTAK_Server_name, + api: process.env.CLOUDTAK_Server_api, + webtak: process.env.CLOUDTAK_Server_webtak + }; + + process.env.CLOUDTAK_Server_name = 'Multi Field Server'; + process.env.CLOUDTAK_Server_api = 'https://multi.test.com:8443'; + process.env.CLOUDTAK_Server_webtak = 'http://multi.test.com:8444'; + + try { + const initialServer = await flight.config.models.Server.from(1); + + const updatedServer = await flight.config.models.Server.commit(initialServer.id, { + name: process.env.CLOUDTAK_Server_name, + api: process.env.CLOUDTAK_Server_api, + webtak: process.env.CLOUDTAK_Server_webtak + }); + + t.equal(updatedServer.name, 'Multi Field Server'); + t.equal(updatedServer.api, 'https://multi.test.com:8443'); + t.equal(updatedServer.webtak, 'http://multi.test.com:8444'); + } catch (err) { + t.error(err, 'no error'); + } + + // Restore environment + Object.keys(originalVars).forEach(key => { + const envVar = `CLOUDTAK_Server_${key}`; + if (originalVars[key] !== undefined) { + process.env[envVar] = originalVars[key]; + } else { + delete process.env[envVar]; + } + }); + + t.end(); +}); + +test('Server Env: schema validation with invalid field', async (t) => { + try { + const initialServer = await flight.config.models.Server.from(1); + + // This should fail if schema validation is working + await flight.config.models.Server.commit(initialServer.id, { + invalidField: 'should-fail' + }); + + t.fail('Should have thrown error for invalid field'); + } catch { + t.pass('Correctly rejected invalid field'); + } + + t.end(); +}); + flight.landing(); diff --git a/api/test/flight.ts b/api/test/flight.ts index a350a170b..7e5c7a53a 100644 --- a/api/test/flight.ts +++ b/api/test/flight.ts @@ -243,7 +243,7 @@ export default class Flight { Object.assign(this.config, custom); - this.config.models.Server.generate({ + await this.config.models.Server.generate({ name: 'Test Runner', url: 'ssl://localhost', auth: { diff --git a/docs/ENVIRONMENT_VARIABLES.md b/docs/ENVIRONMENT_VARIABLES.md new file mode 100644 index 000000000..70bc04606 --- /dev/null +++ b/docs/ENVIRONMENT_VARIABLES.md @@ -0,0 +1,231 @@ +# CloudTAK Environment Variables + +CloudTAK supports configuration through environment variables that override database settings when present at startup. This document lists all available configuration environment variables. + +## Server Configuration Variables + +These variables configure the TAK server connection and are processed during application startup to solve the first-boot configuration problem. + +### TAK Server Configuration + +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_Server_name` | string | TAK Server name/description | +| `CLOUDTAK_Server_url` | string | TAK Server connection URL (e.g., "ssl://tak.example.com:8089") | +| `CLOUDTAK_Server_api` | string | TAK Server API URL (e.g., "https://tak.example.com:8443") | +| `CLOUDTAK_Server_webtak` | string | TAK Server WebTAK URL (e.g., "https://tak.example.com:8446") | + +### TAK Server Authentication + +CloudTAK supports multiple authentication methods for connecting to TAK servers: + +#### Option 1: P12/PKCS12 Certificate (Recommended for AWS) +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_Server_auth_p12_secret_arn` | string | AWS Secrets Manager ARN containing P12 certificate | +| `CLOUDTAK_Server_auth_password` | string | Password for P12/PKCS12 file | + +#### Option 2: Direct Certificate and Key +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_Server_auth_cert` | string | TAK Server client certificate (PEM format) | +| `CLOUDTAK_Server_auth_key` | string | TAK Server client private key (PEM format) | + +### Admin User Configuration + +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_ADMIN_USERNAME` | string | Admin username for CloudTAK system | +| `CLOUDTAK_ADMIN_PASSWORD` | string | Admin password for CloudTAK system | + +## Application Configuration Variables + +These variables follow the pattern `CLOUDTAK_Config_` and override database settings. + +### Format Rules +- Prefix: `CLOUDTAK_Config_` +- Replace `::` with `_` in the config key +- All characters after `CLOUDTAK_Config_` are case SENSITIVE + +**Example:** `media::url` becomes `CLOUDTAK_Config_media_url` + +### ArcGIS Online Configuration + +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_Config_agol_enabled` | boolean | Enable/disable ArcGIS Online integration | +| `CLOUDTAK_Config_agol_token` | string | ArcGIS Online API token | + +### Media Server Configuration + +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_Config_media_url` | string | CloudTAK Hosted MediaMTX Service URL | + +### Map Configuration + +| Environment Variable | Type | Default | Description | +|---------------------|------|---------|-------------| +| `CLOUDTAK_Config_map_center` | string | "-100,40" | Initial map center coordinates | +| `CLOUDTAK_Config_map_pitch` | integer (0-90) | 0 | Initial map pitch | +| `CLOUDTAK_Config_map_bearing` | integer (0-360) | 0 | Initial map bearing | +| `CLOUDTAK_Config_map_zoom` | integer (0-20) | 4 | Initial map zoom level | + +### TAK User Group Configuration + +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_Config_group_Yellow` | string | Yellow group configuration | +| `CLOUDTAK_Config_group_Cyan` | string | Cyan group configuration | +| `CLOUDTAK_Config_group_Green` | string | Green group configuration | +| `CLOUDTAK_Config_group_Red` | string | Red group configuration | +| `CLOUDTAK_Config_group_Purple` | string | Purple group configuration | +| `CLOUDTAK_Config_group_Orange` | string | Orange group configuration | +| `CLOUDTAK_Config_group_Blue` | string | Blue group configuration | +| `CLOUDTAK_Config_group_Magenta` | string | Magenta group configuration | +| `CLOUDTAK_Config_group_White` | string | White group configuration | +| `CLOUDTAK_Config_group_Maroon` | string | Maroon group configuration | +| `CLOUDTAK_Config_group_Dark_Blue` | string | Dark Blue group configuration | +| `CLOUDTAK_Config_group_Teal` | string | Teal group configuration | +| `CLOUDTAK_Config_group_Dark_Green` | string | Dark Green group configuration | +| `CLOUDTAK_Config_group_Brown` | string | Brown group configuration | + +### OIDC (OpenID Connect) Configuration + +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_Config_oidc_enabled` | boolean | Enable/disable OIDC authentication | +| `CLOUDTAK_Config_oidc_enforced` | boolean | Enforce OIDC authentication | +| `CLOUDTAK_Config_oidc_name` | string | OIDC provider name | +| `CLOUDTAK_Config_oidc_discovery` | string | OIDC discovery URL | +| `CLOUDTAK_Config_oidc_client` | string | OIDC client ID | +| `CLOUDTAK_Config_oidc_secret` | string | OIDC client secret | + +### COTAK OAuth Provider Configuration + +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_Config_provider_url` | string | Provider URL | +| `CLOUDTAK_Config_provider_secret` | string | Provider secret | +| `CLOUDTAK_Config_provider_client` | string | Provider client | + +### Login Page Configuration + +| Environment Variable | Type | Description | +|---------------------|------|-------------| +| `CLOUDTAK_Config_login_signup` | string | TAK Server signup link URL | +| `CLOUDTAK_Config_login_forgot` | string | TAK Server password reset link URL | +| `CLOUDTAK_Config_login_logo` | string | Base64 encoded login logo (must start with "data:image/png;base64,") | + +## Usage Examples + +### TAK Server Configuration with P12 Certificate (AWS) +```bash +# Configure TAK Server connection +export CLOUDTAK_Server_name="Production TAK Server" +export CLOUDTAK_Server_url="ssl://tak.example.com:8089" +export CLOUDTAK_Server_api="https://tak.example.com:8443" +export CLOUDTAK_Server_webtak="https://tak.example.com:8446" + +# Use P12 certificate from AWS Secrets Manager +export CLOUDTAK_Server_auth_p12_secret_arn="arn:aws:secretsmanager:region:account:secret:tak-cert" +export CLOUDTAK_Server_auth_password="atakatak" + +# Admin user configuration +export CLOUDTAK_ADMIN_USERNAME="admin" +export CLOUDTAK_ADMIN_PASSWORD="secure-password" +``` + +### TAK Server Configuration with Direct Certificates +```bash +# Configure TAK Server connection +export CLOUDTAK_Server_name="Development TAK Server" +export CLOUDTAK_Server_url="ssl://tak-dev.example.com:8089" +export CLOUDTAK_Server_api="https://tak-dev.example.com:8443" +export CLOUDTAK_Server_webtak="https://tak-dev.example.com:8446" + +# Use direct certificate and key +export CLOUDTAK_Server_auth_cert="-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----" +export CLOUDTAK_Server_auth_key="-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----" +``` + +### Application Configuration +```bash +# Set map center to New Zealand coordinates +export CLOUDTAK_Config_map_center="-41.2865,174.7762" +export CLOUDTAK_Config_map_zoom=6 + +# Enable ArcGIS Online +export CLOUDTAK_Config_agol_enabled=true +export CLOUDTAK_Config_agol_token="your_agol_token_here" + +# Configure emergency response groups +export CLOUDTAK_Config_group_Red="Emergency Response Team" +export CLOUDTAK_Config_group_Blue="Police Units" +export CLOUDTAK_Config_group_Green="Medical Teams" +``` + +### OIDC Authentication +```bash +# Configure OIDC authentication +export CLOUDTAK_Config_oidc_enabled=true +export CLOUDTAK_Config_oidc_enforced=false +export CLOUDTAK_Config_oidc_name="Corporate SSO" +export CLOUDTAK_Config_oidc_discovery="https://your-oidc-provider.com/.well-known/openid_configuration" +export CLOUDTAK_Config_oidc_client="your-client-id" +export CLOUDTAK_Config_oidc_secret="your-client-secret" +``` + +### Docker Compose Example +```yaml +version: '3.8' +services: + cloudtak: + image: cloudtak:latest + environment: + # TAK Server Configuration + - CLOUDTAK_Server_name=Production TAK Server + - CLOUDTAK_Server_url=ssl://tak.example.com:8089 + - CLOUDTAK_Server_api=https://tak.example.com:8443 + - CLOUDTAK_Server_webtak=https://tak.example.com:8446 + - CLOUDTAK_Server_auth_cert=-----BEGIN CERTIFICATE-----... + - CLOUDTAK_Server_auth_key=-----BEGIN PRIVATE KEY-----... + + # Admin Configuration + - CLOUDTAK_ADMIN_USERNAME=admin + - CLOUDTAK_ADMIN_PASSWORD=secure-password + + # Application Configuration + - CLOUDTAK_Config_map_center=-41.2865,174.7762 + - CLOUDTAK_Config_map_zoom=6 + - CLOUDTAK_Config_agol_enabled=true + - CLOUDTAK_Config_agol_token=your_token_here + - CLOUDTAK_Config_group_Red=Emergency Response +``` + +## Configuration Processing + +### Server Configuration +1. **First Boot**: If no server configuration exists in the database, CloudTAK creates initial server configuration using environment variables +2. **Runtime Updates**: Server configuration environment variables override database values when present at startup +3. **Certificate Handling**: Supports both P12/PKCS12 files (via AWS Secrets Manager) and direct PEM certificates +4. **Admin User Creation**: Automatically creates admin user with system admin permissions when credentials are provided + +### Application Configuration +1. **Database Override**: `CLOUDTAK_Config_*` variables override corresponding database settings +2. **Startup Processing**: All matching environment variables are processed and stored in the database during startup +3. **Case Sensitivity**: Configuration keys after `CLOUDTAK_Config_` are case-sensitive +4. **Key Transformation**: `::` in database keys becomes `_` in environment variable names + +## Important Notes + +- **Server configuration** environment variables solve the first-boot configuration problem by allowing automated TAK server setup without requiring API authentication +- Environment variables present at launch will **OVERRIDE** any values stored in the database +- All characters after `CLOUDTAK_Config_` are case sensitive +- Boolean values should be set as `true` or `false` +- Integer values should be provided as numbers without quotes +- String values can be provided with or without quotes +- Empty string values will be treated as valid configuration +- For certificate/key values, use proper PEM format with escaped newlines (`\n`) in environment variables +- P12 certificates from AWS Secrets Manager are automatically converted to PEM format +- Admin users created via environment variables receive full system administrator privileges