14 KiB
File-Based Configuration Architecture Design
Overview
This document outlines the XDG-compliant file-based configuration system for the C Nostr Relay, following the Ginxsom admin system approach using signed Nostr events.
XDG Base Directory Specification Compliance
File Location Strategy
Primary Location:
$XDG_CONFIG_HOME/c-relay/c_relay_config_event.json
Fallback Location:
$HOME/.config/c-relay/c_relay_config_event.json
System-wide Fallback:
/etc/c-relay/c_relay_config_event.json
Directory Structure
$XDG_CONFIG_HOME/c-relay/
├── c_relay_config_event.json # Main configuration file
├── backup/ # Configuration backups
│ ├── c_relay_config_event.json.bak
│ └── c_relay_config_event.20241205.json
└── validation/ # Validation logs
└── config_validation.log
Configuration File Format
Signed Nostr Event Structure
The configuration file contains a signed Nostr event (kind 33334) with relay configuration:
{
"kind": 33334,
"created_at": 1704067200,
"tags": [
["relay_name", "C Nostr Relay"],
["relay_description", "High-performance C Nostr relay with SQLite storage"],
["relay_port", "8888"],
["database_path", "db/c_nostr_relay.db"],
["admin_pubkey", ""],
["admin_enabled", "false"],
["pow_enabled", "true"],
["pow_min_difficulty", "0"],
["pow_mode", "basic"],
["expiration_enabled", "true"],
["expiration_strict", "true"],
["expiration_filter", "true"],
["expiration_grace_period", "300"],
["max_subscriptions_per_client", "20"],
["max_total_subscriptions", "5000"],
["max_connections", "100"],
["relay_contact", ""],
["relay_pubkey", ""],
["relay_software", "https://git.laantungir.net/laantungir/c-relay.git"],
["relay_version", "0.2.0"],
["max_event_tags", "100"],
["max_content_length", "8196"],
["max_message_length", "16384"],
["default_limit", "500"],
["max_limit", "5000"]
],
"content": "C Nostr Relay configuration event",
"pubkey": "admin_public_key_hex_64_chars",
"id": "computed_event_id_hex_64_chars",
"sig": "computed_signature_hex_128_chars"
}
Event Kind Definition
Kind 33334: C Nostr Relay Configuration Event
- Parameterized replaceable event
- Must be signed by authorized admin pubkey
- Contains relay configuration as tags
- Validation required on load
Configuration Loading Architecture
Loading Priority Chain
- Command Line Arguments (highest priority)
- File-based Configuration (signed Nostr event)
- Database Configuration (persistent storage)
- Environment Variables (compatibility mode)
- Hardcoded Defaults (fallback)
Loading Process Flow
flowchart TD
A[Server Startup] --> B[Get Config File Path]
B --> C{File Exists?}
C -->|No| D[Check Database Config]
C -->|Yes| E[Load & Parse JSON]
E --> F[Validate Event Structure]
F --> G{Valid Event?}
G -->|No| H[Log Error & Use Database]
G -->|Yes| I[Verify Event Signature]
I --> J{Signature Valid?}
J -->|No| K[Log Error & Use Database]
J -->|Yes| L[Extract Configuration Tags]
L --> M[Apply to Database]
M --> N[Apply to Application]
D --> O[Load from Database]
H --> O
K --> O
O --> P[Apply Environment Variable Overrides]
P --> Q[Apply Command Line Overrides]
Q --> N
N --> R[Server Ready]
C Implementation Architecture
Core Data Structures
// Configuration file management
typedef struct {
char file_path[512];
char file_hash[65]; // SHA256 hash
time_t last_modified;
time_t last_loaded;
int validation_status; // 0=valid, 1=invalid, 2=unverified
char validation_error[256];
} config_file_info_t;
// Configuration event structure
typedef struct {
char event_id[65];
char pubkey[65];
char signature[129];
long created_at;
int kind;
cJSON* tags;
char* content;
} config_event_t;
// Configuration management context
typedef struct {
config_file_info_t file_info;
config_event_t event;
int loaded_from_file;
int loaded_from_database;
char admin_pubkey[65];
time_t load_timestamp;
} config_context_t;
Core Function Signatures
// XDG path resolution
int get_config_file_path(char* path, size_t path_size);
int create_config_directories(const char* config_path);
// File operations
int load_config_from_file(const char* config_path, config_context_t* ctx);
int save_config_to_file(const char* config_path, const config_event_t* event);
int backup_config_file(const char* config_path);
// Event validation
int validate_config_event_structure(const cJSON* event);
int verify_config_event_signature(const config_event_t* event, const char* admin_pubkey);
int validate_config_tag_values(const cJSON* tags);
// Configuration extraction and application
int extract_config_from_tags(const cJSON* tags, config_context_t* ctx);
int apply_config_to_database(const config_context_t* ctx);
int apply_config_to_globals(const config_context_t* ctx);
// File monitoring and updates
int monitor_config_file_changes(const char* config_path);
int reload_config_on_change(config_context_t* ctx);
// Error handling and logging
int log_config_validation_error(const char* config_key, const char* error);
int log_config_load_event(const config_context_t* ctx, const char* source);
Configuration Validation Rules
Event Structure Validation
- Required Fields:
kind,created_at,tags,content,pubkey,id,sig - Kind Validation: Must be exactly 33334
- Timestamp Validation: Must be reasonable (not too old, not future)
- Tags Format: Array of string arrays
[["key", "value"], ...] - Signature Verification: Must be signed by authorized admin pubkey
Configuration Value Validation
typedef struct {
char* key;
char* data_type; // "string", "integer", "boolean", "json"
char* validation_rule; // JSON validation rule
int required;
char* default_value;
} config_validation_rule_t;
static config_validation_rule_t validation_rules[] = {
{"relay_port", "integer", "{\"min\": 1, \"max\": 65535}", 1, "8888"},
{"pow_min_difficulty", "integer", "{\"min\": 0, \"max\": 64}", 1, "0"},
{"expiration_grace_period", "integer", "{\"min\": 0, \"max\": 86400}", 1, "300"},
{"admin_pubkey", "string", "{\"pattern\": \"^[0-9a-fA-F]{64}$\"}", 0, ""},
{"pow_enabled", "boolean", "{}", 1, "true"},
// ... more rules
};
Security Validation
- Admin Pubkey Verification: Only configured admin pubkeys can create config events
- Event ID Verification: Event ID must match computed hash
- Signature Verification: Signature must be valid for the event and pubkey
- Timestamp Validation: Prevent replay attacks with old events
- File Permission Checks: Config files should have appropriate permissions
File Management Features
Configuration File Operations
File Creation:
- Generate initial configuration file with default values
- Sign with admin private key
- Set appropriate file permissions (600 - owner read/write only)
File Updates:
- Create backup of existing file
- Validate new configuration
- Atomic file replacement (write to temp, then rename)
- Update file metadata cache
File Monitoring:
- Watch for file system changes using inotify (Linux)
- Reload configuration automatically when file changes
- Validate changes before applying
- Log all configuration reload events
Backup and Recovery
Automatic Backups:
$XDG_CONFIG_HOME/c-relay/backup/
├── c_relay_config_event.json.bak # Last working config
├── c_relay_config_event.20241205-143022.json # Timestamped backups
└── c_relay_config_event.20241204-091530.json
Recovery Process:
- Detect corrupted or invalid config file
- Attempt to load from
.bakbackup - If backup fails, generate default configuration
- Log recovery actions for audit
Integration with Database Schema
File-Database Synchronization
On File Load:
- Parse and validate file-based configuration
- Extract configuration values from event tags
- Update database
server_configtable - Record file metadata in
config_file_cachetable - Log configuration changes in
config_historytable
Configuration Priority Resolution:
char* get_config_value(const char* key, const char* default_value) {
// Priority: CLI args > File config > DB config > Env vars > Default
char* value = NULL;
// 1. Check command line overrides (if implemented)
value = get_cli_override(key);
if (value) return value;
// 2. Check database (updated from file)
value = get_database_config(key);
if (value) return value;
// 3. Check environment variables (compatibility)
value = get_env_config(key);
if (value) return value;
// 4. Return default
return strdup(default_value);
}
Error Handling and Recovery
Validation Error Handling
typedef enum {
CONFIG_ERROR_NONE = 0,
CONFIG_ERROR_FILE_NOT_FOUND = 1,
CONFIG_ERROR_PARSE_FAILED = 2,
CONFIG_ERROR_INVALID_STRUCTURE = 3,
CONFIG_ERROR_SIGNATURE_INVALID = 4,
CONFIG_ERROR_UNAUTHORIZED = 5,
CONFIG_ERROR_VALUE_INVALID = 6,
CONFIG_ERROR_IO_ERROR = 7
} config_error_t;
typedef struct {
config_error_t error_code;
char error_message[256];
char config_key[64];
char invalid_value[128];
time_t error_timestamp;
} config_error_info_t;
Graceful Degradation
File Load Failure:
- Log detailed error information
- Fall back to database configuration
- Continue operation with last known good config
- Set service status to "degraded" mode
Validation Failure:
- Log validation errors with specific details
- Skip invalid configuration items
- Use default values for failed items
- Continue with partial configuration
Permission Errors:
- Log permission issues
- Attempt to use fallback locations
- Generate temporary config if needed
- Alert administrator via logs
Configuration Update Process
Safe Configuration Updates
Atomic Update Process:
- Create backup of current configuration
- Write new configuration to temporary file
- Validate new configuration completely
- If valid, rename temporary file to active config
- Update database with new values
- Apply changes to running server
- Log successful update
Rollback Process:
- Detect invalid configuration at startup
- Restore from backup file
- Log rollback event
- Continue with previous working configuration
Hot Reload Support
File Change Detection:
int monitor_config_file_changes(const char* config_path) {
// Use inotify on Linux to watch file changes
int inotify_fd = inotify_init();
int watch_fd = inotify_add_watch(inotify_fd, config_path, IN_MODIFY | IN_MOVED_TO);
// Monitor in separate thread
// On change: validate -> apply -> log
return 0;
}
Runtime Configuration Updates:
- Reload configuration on file change
- Apply non-restart-required changes immediately
- Queue restart-required changes for next restart
- Notify operators of configuration changes
Security Considerations
Access Control
File Permissions:
- Config files: 600 (owner read/write only)
- Directories: 700 (owner access only)
- Backup files: 600 (owner read/write only)
Admin Key Management:
- Admin private keys never stored in config files
- Only admin pubkeys stored for verification
- Support for multiple admin pubkeys
- Key rotation support
Signature Validation
Event Signature Verification:
int verify_config_event_signature(const config_event_t* event, const char* admin_pubkey) {
// 1. Reconstruct event for signing (without id and sig)
// 2. Compute event ID and verify against stored ID
// 3. Verify signature using admin pubkey
// 4. Check admin pubkey authorization
return NOSTR_SUCCESS;
}
Anti-Replay Protection:
- Configuration events must be newer than current
- Event timestamps validated against reasonable bounds
- Configuration history prevents replay attacks
Implementation Phases
Phase 1: Basic File Support
- XDG path resolution
- File loading and parsing
- Basic validation
- Database integration
Phase 2: Security Features
- Event signature verification
- Admin pubkey management
- File permission checks
- Error handling
Phase 3: Advanced Features
- Hot reload support
- Automatic backups
- Configuration utilities
- Interactive setup
Phase 4: Monitoring & Management
- Configuration change monitoring
- Advanced validation rules
- Configuration audit logging
- Management utilities
Configuration Generation Utilities
Interactive Setup Script
#!/bin/bash
# scripts/setup_config.sh - Interactive configuration setup
create_initial_config() {
echo "=== C Nostr Relay Initial Configuration ==="
# Collect basic information
read -p "Relay name [C Nostr Relay]: " relay_name
read -p "Admin public key (hex): " admin_pubkey
read -p "Server port [8888]: " server_port
# Generate signed configuration event
./scripts/generate_config.sh \
--admin-key "$admin_pubkey" \
--relay-name "${relay_name:-C Nostr Relay}" \
--port "${server_port:-8888}" \
--output "$XDG_CONFIG_HOME/c-relay/c_relay_config_event.json"
}
Configuration Validation Utility
#!/bin/bash
# scripts/validate_config.sh - Validate configuration file
validate_config_file() {
local config_file="$1"
# Check file exists and is readable
# Validate JSON structure
# Verify event signature
# Check configuration values
# Report validation results
}
This comprehensive file-based configuration design provides a robust, secure, and maintainable system that follows industry standards while integrating seamlessly with the existing C Nostr Relay architecture.