163 lines
5.3 KiB
Dart
163 lines
5.3 KiB
Dart
import 'dart:io';
|
|
import 'dart:convert';
|
|
import 'dart:math';
|
|
|
|
import 'package:yaml/yaml.dart';
|
|
|
|
class Config {
|
|
static late String bindHost;
|
|
static late int bindPort;
|
|
static late String publicBaseUrl;
|
|
static late String serverName;
|
|
static late String jwtSecret;
|
|
static late bool configured;
|
|
static late bool setupMode;
|
|
static late int bootstrapAdminLimit;
|
|
static late bool devDbTools;
|
|
|
|
static const Duration tokenExpiry = Duration(days: 7);
|
|
|
|
static void initialize() {
|
|
final configFile = File('config.yaml');
|
|
|
|
if (!configFile.existsSync()) {
|
|
final generatedSecret = _generateJwtSecret();
|
|
configFile.writeAsStringSync(_defaultConfig(generatedSecret));
|
|
print('✅ Generated config.yaml (first run).');
|
|
print('⚠️ Review server.public_base_url and security.jwt_secret before production use.');
|
|
}
|
|
|
|
final yamlString = configFile.readAsStringSync();
|
|
final yamlMap = loadYaml(yamlString) as YamlMap;
|
|
|
|
final serverMap = yamlMap['server'] as YamlMap?;
|
|
final securityMap = yamlMap['security'] as YamlMap?;
|
|
|
|
serverName = (serverMap?['name'] as String?)?.trim() ?? '';
|
|
bindHost = (serverMap?['bind_host'] as String?)?.trim() ?? (serverMap?['host'] as String?)?.trim() ?? '127.0.0.1';
|
|
bindPort = (serverMap?['bind_port'] as int?) ?? (serverMap?['port'] as int?) ?? 9870;
|
|
final rawPublicBaseUrl =
|
|
(serverMap?['public_base_url'] as String?)?.trim() ?? _legacyPublicBaseUrlFromHost((serverMap?['host'] as String?)?.trim());
|
|
publicBaseUrl = _normalizePublicBaseUrl(rawPublicBaseUrl) ?? '';
|
|
configured = (serverMap?['configured'] as bool?) ?? false;
|
|
setupMode = (serverMap?['first_setup'] as bool?) ?? false;
|
|
bootstrapAdminLimit = (serverMap?['bootstrap_admin_limit'] as int?) ?? 1;
|
|
devDbTools = (serverMap?['dev_db_tools'] as bool?) ?? false;
|
|
|
|
jwtSecret = (securityMap?['jwt_secret'] as String?)?.trim() ?? '';
|
|
|
|
if (serverName.isEmpty) {
|
|
throw StateError('config.yaml: server.name is required.');
|
|
}
|
|
if (bindHost.isEmpty) {
|
|
throw StateError('config.yaml: server.bind_host is required.');
|
|
}
|
|
if (bindPort <= 0 || bindPort > 65535) {
|
|
throw StateError('config.yaml: server.bind_port must be between 1 and 65535.');
|
|
}
|
|
if (publicBaseUrl.isEmpty) {
|
|
throw StateError(
|
|
'config.yaml: server.public_base_url is required (example: https://chat.example.com).',
|
|
);
|
|
}
|
|
if (jwtSecret.isEmpty) {
|
|
throw StateError('config.yaml: security.jwt_secret is required.');
|
|
}
|
|
if (!configured) {
|
|
throw StateError(
|
|
'Server setup is not complete.\n'
|
|
'Edit config.yaml and set:\n'
|
|
' server.configured: true\n'
|
|
'After reviewing server.public_base_url and security.jwt_secret.',
|
|
);
|
|
}
|
|
if (bootstrapAdminLimit < 0) {
|
|
throw StateError('config.yaml: server.bootstrap_admin_limit must be >= 0.');
|
|
}
|
|
|
|
print(
|
|
'🚀 Server Config Loaded: $serverName (bind=$bindHost:$bindPort, public=$publicBaseUrl, configured=$configured, setupMode=$setupMode, bootstrapAdminLimit=$bootstrapAdminLimit, devDbTools=$devDbTools)',
|
|
);
|
|
}
|
|
|
|
static String _generateJwtSecret() {
|
|
final random = Random.secure();
|
|
final bytes = List<int>.generate(32, (_) => random.nextInt(256));
|
|
return base64UrlEncode(bytes);
|
|
}
|
|
|
|
static String _defaultConfig(String generatedSecret) => '''
|
|
# Reliquary Server Configuration
|
|
server:
|
|
# Display name shown in clients
|
|
name: "Reliquary Server"
|
|
|
|
# Local bind address for the HTTP server
|
|
bind_host: "127.0.0.1"
|
|
|
|
# Local bind port for the HTTP server
|
|
bind_port: 9870
|
|
|
|
# Public base URL used for identity, handles, and cross-server references
|
|
# Dev example: http://localhost:9870
|
|
# Prod example: https://chat.example.com
|
|
public_base_url: "http://localhost:9870"
|
|
|
|
# Set to true after reviewing this file; server refuses to start while false
|
|
configured: false
|
|
|
|
# First local registration becomes admin
|
|
first_setup: true
|
|
|
|
# Number of local registrations auto-promoted to admin while first_setup=true
|
|
bootstrap_admin_limit: 1
|
|
|
|
# Testing-only DB admin/debug endpoints
|
|
dev_db_tools: true
|
|
|
|
security:
|
|
# Auto-generated on first run; replace for production
|
|
jwt_secret: "$generatedSecret"
|
|
''';
|
|
|
|
static String get publicHost {
|
|
final uri = Uri.tryParse(publicBaseUrl);
|
|
if (uri == null || uri.host.isEmpty) {
|
|
return publicBaseUrl;
|
|
}
|
|
return uri.hasPort ? '${uri.host}:${uri.port}' : uri.host;
|
|
}
|
|
|
|
static String? _normalizePublicBaseUrl(String? value) {
|
|
if (value == null || value.trim().isEmpty) {
|
|
return null;
|
|
}
|
|
final uri = Uri.tryParse(value.trim());
|
|
if (uri == null || uri.host.isEmpty) {
|
|
return null;
|
|
}
|
|
if (uri.scheme != 'http' && uri.scheme != 'https') {
|
|
return null;
|
|
}
|
|
return Uri(
|
|
scheme: uri.scheme,
|
|
host: uri.host,
|
|
port: uri.hasPort ? uri.port : null,
|
|
).toString().replaceAll(RegExp(r'/$'), '');
|
|
}
|
|
|
|
static String? _legacyPublicBaseUrlFromHost(String? hostValue) {
|
|
if (hostValue == null || hostValue.trim().isEmpty) {
|
|
return null;
|
|
}
|
|
final host = hostValue.trim();
|
|
if (host.contains('://')) {
|
|
return host;
|
|
}
|
|
final lower = host.toLowerCase();
|
|
final isLocal = lower.startsWith('localhost') || lower.startsWith('127.') || lower.startsWith('[::1]');
|
|
final scheme = isLocal ? 'http' : 'https';
|
|
return '$scheme://$host';
|
|
}
|
|
}
|