const std = @import("std"); var public_url_value: []const u8 = "http://localhost:2583"; var server_did_value: []const u8 = "did:web:localhost"; var plc_directory_value: []const u8 = "https://plc.directory"; var plc_rotation_key_value: ?[]const u8 = null; var recovery_did_key_value: ?[]const u8 = null; var mail_provider_value: MailProvider = .comail; var resend_api_key_value: ?[]const u8 = null; var comail_api_key_value: ?[]const u8 = null; var comail_did_value: ?[]const u8 = null; var email_from_value: ?[]const u8 = null; var blob_upload_limit_value: usize = 100_000_000; var blobstore_path_value: []const u8 = "dev/blobs"; var handle_domains_value: []const u8 = ".test"; var crawlers_value: []const u8 = "https://bsky.network,https://vsky.network"; var operator_handle_value: []const u8 = ""; var max_concurrent_repo_exports_value: usize = 4; var proxy_service_did_value: []const u8 = "did:web:api.bsky.app"; var proxy_service_id_value: []const u8 = "bsky_appview"; var proxy_service_url_value: []const u8 = "https://api.bsky.app"; var jwt_secret_value: []const u8 = "zds-local-development-jwt-secret-change-me"; var dpop_secret_value: ?[]const u8 = null; var admin_token_value: ?[]const u8 = null; var invite_required_value: bool = false; var permissioned_data_value: bool = false; var log_level_value: LogLevel = .info; pub const LogLevel = enum(u2) { err = 0, info = 1, debug = 2, }; pub const MailProvider = enum { comail, resend, }; pub fn publicUrl() []const u8 { return public_url_value; } pub fn serverDid() []const u8 { return server_did_value; } pub fn plcDirectory() []const u8 { return plc_directory_value; } pub fn plcRotationKey() ?[]const u8 { return plc_rotation_key_value; } pub fn recoveryDidKey() ?[]const u8 { return recovery_did_key_value; } pub fn mailProvider() MailProvider { return mail_provider_value; } pub fn resendApiKey() ?[]const u8 { return resend_api_key_value; } pub fn comailApiKey() ?[]const u8 { return comail_api_key_value; } pub fn comailDid() ?[]const u8 { return comail_did_value; } pub fn emailFrom() ?[]const u8 { return email_from_value; } pub fn blobUploadLimit() usize { return blob_upload_limit_value; } pub fn blobstorePath() []const u8 { return blobstore_path_value; } pub fn handleDomains() []const u8 { return handle_domains_value; } pub fn crawlers() []const u8 { return crawlers_value; } /// atproto handle of the person operating this PDS, or empty when unset. /// Shown on the public signup page as the invite contact. pub fn operatorHandle() []const u8 { return operator_handle_value; } pub fn maxConcurrentRepoExports() usize { return max_concurrent_repo_exports_value; } pub fn proxyServiceDid() []const u8 { return proxy_service_did_value; } pub fn proxyServiceId() []const u8 { return proxy_service_id_value; } pub fn proxyServiceUrl() []const u8 { return proxy_service_url_value; } pub fn jwtSecret() []const u8 { return jwt_secret_value; } pub fn dpopSecret() []const u8 { return dpop_secret_value orelse jwt_secret_value; } pub fn adminToken() ?[]const u8 { return admin_token_value; } pub fn inviteRequired() bool { return invite_required_value; } pub fn permissionedData() bool { return permissioned_data_value; } pub fn logLevel() LogLevel { return log_level_value; } pub fn logEnabled(level: LogLevel) bool { return @backingInt(level) <= @backingInt(log_level_value); } pub fn setPublicUrl(value: []const u8) void { public_url_value = trimTrailingSlash(value); } pub fn setServerDid(value: []const u8) void { server_did_value = value; } var derived_server_did_buf: [512]u8 = undefined; /// `did:web:` for a public URL, the DID a PDS is expected to answer to /// when none is configured explicitly. A port is encoded as `%3A` per did:web. pub fn didWebForUrl(buf: []u8, url: []const u8) error{InvalidPublicUrl}![]const u8 { const scheme_end = std.mem.indexOf(u8, url, "://") orelse return error.InvalidPublicUrl; var authority = url[scheme_end + 3 ..]; if (std.mem.indexOfScalar(u8, authority, '/')) |slash| authority = authority[0..slash]; if (authority.len == 0 or std.mem.indexOfScalar(u8, authority, '@') != null) return error.InvalidPublicUrl; var w = std.Io.Writer.fixed(buf); w.writeAll("did:web:") catch return error.InvalidPublicUrl; for (authority) |c| { if (c == ':') { w.writeAll("%3A") catch return error.InvalidPublicUrl; } else { w.writeByte(c) catch return error.InvalidPublicUrl; } } return w.buffered(); } pub fn deriveServerDidFromPublicUrl() error{InvalidPublicUrl}!void { server_did_value = try didWebForUrl(&derived_server_did_buf, public_url_value); } pub fn setPlcDirectory(value: []const u8) void { plc_directory_value = trimTrailingSlash(value); } pub fn setPlcRotationKey(value: ?[]const u8) void { plc_rotation_key_value = value; } pub fn setRecoveryDidKey(value: ?[]const u8) void { recovery_did_key_value = value; } pub fn setMailProvider(value: MailProvider) void { mail_provider_value = value; } pub fn setResendApiKey(value: ?[]const u8) void { resend_api_key_value = value; } pub fn setComailApiKey(value: ?[]const u8) void { comail_api_key_value = value; } pub fn setComailDid(value: ?[]const u8) void { comail_did_value = value; } pub fn setEmailFrom(value: ?[]const u8) void { email_from_value = value; } pub fn setBlobUploadLimit(value: usize) void { blob_upload_limit_value = value; } pub fn setBlobstorePath(value: []const u8) void { blobstore_path_value = trimTrailingSlash(value); } pub fn setHandleDomains(value: []const u8) void { handle_domains_value = value; } pub fn setCrawlers(value: []const u8) void { crawlers_value = value; } pub fn setOperatorHandle(value: []const u8) void { operator_handle_value = value; } pub fn setMaxConcurrentRepoExports(value: usize) void { max_concurrent_repo_exports_value = value; } pub fn setProxyServiceDid(value: []const u8) void { proxy_service_did_value = value; } pub fn setProxyServiceId(value: []const u8) void { proxy_service_id_value = value; } pub fn setProxyServiceUrl(value: []const u8) void { proxy_service_url_value = trimTrailingSlash(value); } pub fn setJwtSecret(value: []const u8) void { jwt_secret_value = value; } pub fn setDpopSecret(value: ?[]const u8) void { dpop_secret_value = value; } pub fn setAdminToken(value: ?[]const u8) void { admin_token_value = value; } pub fn setInviteRequired(value: bool) void { invite_required_value = value; } pub fn setPermissionedData(value: bool) void { permissioned_data_value = value; } pub fn setLogLevel(value: LogLevel) void { log_level_value = value; } pub fn parseLogLevel(value: []const u8) ?LogLevel { if (std.ascii.eqlIgnoreCase(value, "error")) return .err; if (std.ascii.eqlIgnoreCase(value, "err")) return .err; if (std.ascii.eqlIgnoreCase(value, "info")) return .info; if (std.ascii.eqlIgnoreCase(value, "debug")) return .debug; return null; } pub fn parseMailProvider(value: []const u8) ?MailProvider { if (std.ascii.eqlIgnoreCase(value, "comail")) return .comail; if (std.ascii.eqlIgnoreCase(value, "resend")) return .resend; return null; } fn trimTrailingSlash(value: []const u8) []const u8 { if (value.len > 1 and value[value.len - 1] == '/') return value[0 .. value.len - 1]; return value; } test "trims public URL slash" { const before = publicUrl(); defer setPublicUrl(before); setPublicUrl("https://example.com/"); try std.testing.expectEqualStrings("https://example.com", publicUrl()); } test "trims configured proxy service URL slash" { const before = proxyServiceUrl(); defer setProxyServiceUrl(before); setProxyServiceUrl("https://api.example.com/"); try std.testing.expectEqualStrings("https://api.example.com", proxyServiceUrl()); } test "did:web derived from a public URL" { var buf: [128]u8 = undefined; try std.testing.expectEqualStrings("did:web:pi.chadtmiller.com", try didWebForUrl(&buf, "https://pi.chadtmiller.com")); try std.testing.expectEqualStrings("did:web:pds.zat.dev", try didWebForUrl(&buf, "https://pds.zat.dev/")); try std.testing.expectEqualStrings("did:web:localhost%3A2583", try didWebForUrl(&buf, "http://localhost:2583/xrpc")); try std.testing.expectError(error.InvalidPublicUrl, didWebForUrl(&buf, "pds.zat.dev")); try std.testing.expectError(error.InvalidPublicUrl, didWebForUrl(&buf, "https://")); try std.testing.expectError(error.InvalidPublicUrl, didWebForUrl(&buf, "https://user@host")); }