Major architecture update: stable identity, component validation, media indexing, and protocol docs
This commit is contained in:
1 parent
ce7c97d13a
commit
01c110bbe1
16 files changed
+2718
-468
No files matched your search
+158
-10
@@ -1,14 +1,162 @@
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
class Config {
|
||||
// Server settings
|
||||
static const int port = 9870;
|
||||
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;
|
||||
|
||||
// IMPORTANT: Set this to your actual server host/domain
|
||||
// Used for generating userId: "username@serverHost"
|
||||
static const String serverHost = 'localhost:9870';
|
||||
static const String serverName = 'Chat Server';
|
||||
|
||||
// JWT settings
|
||||
// TODO: Move to environment variable in production
|
||||
static const String jwtSecret = 'your_super_secret_key_change_in_production';
|
||||
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';
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user