Files
c-relay/docs/file_config_design.md
2025-09-06 04:40:59 -04:00

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

  1. Command Line Arguments (highest priority)
  2. File-based Configuration (signed Nostr event)
  3. Database Configuration (persistent storage)
  4. Environment Variables (compatibility mode)
  5. 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

  1. Required Fields: kind, created_at, tags, content, pubkey, id, sig
  2. Kind Validation: Must be exactly 33334
  3. Timestamp Validation: Must be reasonable (not too old, not future)
  4. Tags Format: Array of string arrays [["key", "value"], ...]
  5. 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

  1. Admin Pubkey Verification: Only configured admin pubkeys can create config events
  2. Event ID Verification: Event ID must match computed hash
  3. Signature Verification: Signature must be valid for the event and pubkey
  4. Timestamp Validation: Prevent replay attacks with old events
  5. 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:

  1. Detect corrupted or invalid config file
  2. Attempt to load from .bak backup
  3. If backup fails, generate default configuration
  4. Log recovery actions for audit

Integration with Database Schema

File-Database Synchronization

On File Load:

  1. Parse and validate file-based configuration
  2. Extract configuration values from event tags
  3. Update database server_config table
  4. Record file metadata in config_file_cache table
  5. Log configuration changes in config_history table

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:

  1. Log detailed error information
  2. Fall back to database configuration
  3. Continue operation with last known good config
  4. Set service status to "degraded" mode

Validation Failure:

  1. Log validation errors with specific details
  2. Skip invalid configuration items
  3. Use default values for failed items
  4. Continue with partial configuration

Permission Errors:

  1. Log permission issues
  2. Attempt to use fallback locations
  3. Generate temporary config if needed
  4. Alert administrator via logs

Configuration Update Process

Safe Configuration Updates

Atomic Update Process:

  1. Create backup of current configuration
  2. Write new configuration to temporary file
  3. Validate new configuration completely
  4. If valid, rename temporary file to active config
  5. Update database with new values
  6. Apply changes to running server
  7. Log successful update

Rollback Process:

  1. Detect invalid configuration at startup
  2. Restore from backup file
  3. Log rollback event
  4. 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.