libertaria-stack/capsule-core/src/config.zig

130 lines
4.7 KiB
Zig

//! Configuration for the Libertaria Capsule Node.
const std = @import("std");
pub const NodeConfig = struct {
/// Data directory for persistent state (DB, keys, etc.)
data_dir: []const u8,
/// UTCP bind port (default: 8710)
port: u16 = 8710,
/// Control Socket Path (Unix Domain Socket)
control_socket_path: []const u8 = "",
/// Identity Key Path (Ed25519 private key)
identity_key_path: []const u8 = "",
/// Bootstrap peers (multiaddrs)
bootstrap_peers: [][]const u8 = &.{},
/// Logging level
log_level: std.log.Level = .info,
/// Enable Gateway Service (Layer 1 Coordination)
gateway_enabled: bool = false,
/// Enable Relay Service (Layer 2 Forwarding)
relay_enabled: bool = false,
/// Enable Bridge Service (Layer 3 Protocol Translation)
bridge_enabled: bool = false,
/// QVL minimum trust score for relay selection
relay_trust_threshold: f64 = 0.5,
/// Free allocated memory (strings, slices)
pub fn deinit(self: *NodeConfig, allocator: std.mem.Allocator) void {
allocator.free(self.data_dir);
allocator.free(self.control_socket_path);
allocator.free(self.identity_key_path);
for (self.bootstrap_peers) |peer| {
allocator.free(peer);
}
allocator.free(self.bootstrap_peers);
}
pub fn default(allocator: std.mem.Allocator) !NodeConfig {
// Default data dir: ~/.libertaria (or "data" for MVP)
return NodeConfig{
.data_dir = try allocator.dupe(u8, "data"),
.control_socket_path = try allocator.dupe(u8, "data/capsule.sock"),
.identity_key_path = try allocator.dupe(u8, "data/identity.key"),
.port = 8710,
.gateway_enabled = false,
.relay_enabled = false,
.bridge_enabled = false,
.relay_trust_threshold = 0.5,
};
}
/// Load configuration from a JSON file
pub fn loadFromJsonFile(allocator: std.mem.Allocator, path: []const u8) !NodeConfig {
const file = std.fs.cwd().openFile(path, .{}) catch |err| {
if (err == error.FileNotFound) {
// If config missing, create default
std.log.info("Config file not found at {s}, creating default...", .{path});
const cfg = try NodeConfig.default(allocator);
try cfg.saveToJsonFile(path);
return cfg;
}
return err;
};
defer file.close();
const max_size = 1024 * 1024; // 1MB config limit
const content = try file.readToEndAlloc(allocator, max_size);
defer allocator.free(content);
// Parse JSON
const parsed = try std.json.parseFromSlice(NodeConfig, allocator, content, .{
.allocate = .alloc_always,
});
defer parsed.deinit();
// Deep copy strings because parsed.value shares memory with arena/content in some modes,
// but here we used alloc_always so fields are allocated.
// However, std.json.parseFromSlice returns a Parsed(T) which manages the memory.
// We need to detach or copy the data to return a standalone NodeConfig.
// For simplicity and safety: manually duplicate into new struct.
const cfg = parsed.value;
const data_dir = try allocator.dupe(u8, cfg.data_dir);
const control_socket_path = if (cfg.control_socket_path.len > 0)
try allocator.dupe(u8, cfg.control_socket_path)
else
try std.fmt.allocPrint(allocator, "{s}/capsule.sock", .{data_dir});
const identity_key_path = if (cfg.identity_key_path.len > 0)
try allocator.dupe(u8, cfg.identity_key_path)
else
try std.fmt.allocPrint(allocator, "{s}/identity.key", .{data_dir});
var peers = std.array_list.Managed([]const u8).init(allocator);
for (cfg.bootstrap_peers) |peer| {
try peers.append(try allocator.dupe(u8, peer));
}
return NodeConfig{
.data_dir = data_dir,
.control_socket_path = control_socket_path,
.identity_key_path = identity_key_path,
.port = cfg.port,
.bootstrap_peers = try peers.toOwnedSlice(),
.log_level = cfg.log_level,
.gateway_enabled = cfg.gateway_enabled,
};
}
/// Save configuration to a JSON file
pub fn saveToJsonFile(self: *const NodeConfig, path: []const u8) !void {
const file = try std.fs.cwd().createFile(path, .{});
defer file.close();
var buf = std.array_list.Managed(u8).init(std.heap.page_allocator);
defer buf.deinit();
try buf.writer().print("{f}", .{std.json.fmt(self, .{ .whitespace = .indent_4 })});
try file.writeAll(buf.items);
}
};