Skip to content

Commit efd9a97

Browse files
committed
chore: wip
1 parent 2768415 commit efd9a97

7 files changed

Lines changed: 791 additions & 15 deletions

File tree

packages/cloud/cloud.config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ const mailConfig = {
5555
backupDir: '/var/lib/mail/backups',
5656
},
5757

58+
discord: {
59+
webhookUrl: process.env.DISCORD_WEBHOOK_URL || 'https://discord.com/api/webhooks/1479364487294488596/4x1uwO_FvR-4PZ_bZ1ozkF3imltiNoZtEjM2CBFk30xXQkdF3pSJNsVYXtJ_kwEBQhqB',
60+
},
61+
5862
installUtils: [
5963
'git',
6064
'wget',
@@ -520,6 +524,9 @@ SMTP_MAX_MESSAGE_SIZE=${cfg.server.maxMessageSize}
520524
SMTP_MAX_RECIPIENTS=${cfg.server.maxRecipients}
521525
SMTP_RATE_LIMIT_PER_IP=${cfg.server.rateLimitPerIp}
522526
SMTP_RATE_LIMIT_PER_USER=${cfg.server.rateLimitPerUser}
527+
528+
# Discord Health Monitoring
529+
DISCORD_WEBHOOK_URL=${cfg.discord?.webhookUrl || ''}
523530
ENVEOF
524531
525532
chmod 600 ${cfg.paths.configDir}/mail.env

packages/zig/src/auth/auth.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,11 +699,12 @@ pub const ResetRateLimiter = struct {
699699
pub fn recordAttempt(self: *ResetRateLimiter, identifier: []const u8) !void {
700700
const now = getCurrentTimestamp();
701701

702-
const result = try self.attempts.getOrPut(try self.allocator.dupe(u8, identifier));
702+
const result = try self.attempts.getOrPut(identifier);
703703
if (result.found_existing) {
704704
result.value_ptr.count += 1;
705705
result.value_ptr.last_attempt = now;
706706
} else {
707+
result.key_ptr.* = try self.allocator.dupe(u8, identifier);
707708
result.value_ptr.* = .{
708709
.count = 1,
709710
.first_attempt = now,

packages/zig/src/auth/password.zig

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub const PasswordHasher = struct {
3030
.{
3131
.t = 3, // 3 iterations
3232
.m = 65536, // 64 MB memory
33-
.p = 4, // 4 parallelism
33+
.p = 1, // single-threaded (avoids async I/O requirement)
3434
},
3535
.argon2id,
3636
io_compat.getIo(),
@@ -49,10 +49,10 @@ pub const PasswordHasher = struct {
4949
defer self.allocator.free(hash_b64);
5050
const hash_encoded = encoder.encode(hash_b64, &hash);
5151

52-
// Format: $argon2id$v=19$m=65536,t=3,p=4$<salt_b64>$<hash_b64>
52+
// Format: $argon2id$v=19$m=65536,t=3,p=1$<salt_b64>$<hash_b64>
5353
const encoded = try std.fmt.allocPrint(
5454
self.allocator,
55-
"$argon2id$v=19$m=65536,t=3,p=4${s}${s}",
55+
"$argon2id$v=19$m=65536,t=3,p=1${s}${s}",
5656
.{ salt_encoded, hash_encoded },
5757
);
5858

@@ -62,7 +62,7 @@ pub const PasswordHasher = struct {
6262
/// Verify a password against a hash
6363
pub fn verifyPassword(self: *PasswordHasher, password: []const u8, hash_str: []const u8) !bool {
6464
// Parse the hash string
65-
// Format: $argon2id$v=19$m=65536,t=3,p=4$<salt_b64>$<hash_b64>
65+
// Format: $argon2id$v=19$m=65536,t=3,p=1$<salt_b64>$<hash_b64>
6666
var parts = std.mem.splitSequence(u8, hash_str, "$");
6767

6868
// Skip empty first part
@@ -77,11 +77,11 @@ pub const PasswordHasher = struct {
7777
// Skip version
7878
_ = parts.next();
7979

80-
// Parse parameters (m=65536,t=3,p=4)
80+
// Parse parameters (m=65536,t=3,p=1)
8181
const params_str = parts.next() orelse return error.InvalidHashFormat;
8282
var m: u32 = 65536;
8383
var t: u32 = 3;
84-
var p: u24 = 4;
84+
var p: u24 = 1;
8585

8686
var param_parts = std.mem.splitScalar(u8, params_str, ',');
8787
while (param_parts.next()) |param| {

packages/zig/src/main.zig

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const cluster = @import("infrastructure/cluster.zig");
1616
const multitenancy = @import("features/multitenancy.zig");
1717
const metrics_mod = @import("observability/metrics.zig");
1818
const alerting = @import("observability/alerting.zig");
19+
const discord = @import("observability/discord.zig");
1920
const secrets = @import("security/secrets.zig");
2021
const hot_reload = @import("core/hot_reload.zig");
2122

@@ -542,6 +543,26 @@ pub fn run(allocator: std.mem.Allocator, cli_args: args_parser.Args) !void {
542543
}
543544
}
544545

546+
// Start Discord health monitor if webhook URL is configured
547+
var discord_monitor: ?discord.DiscordHealthMonitor = null;
548+
var discord_thread: ?std.Thread = null;
549+
const discord_webhook_url = env.get("DISCORD_WEBHOOK_URL");
550+
if (discord_webhook_url) |webhook_url| {
551+
discord_monitor = discord.DiscordHealthMonitor.init(
552+
allocator,
553+
webhook_url,
554+
&server.active_connections,
555+
cfg.max_connections,
556+
&shutdown_requested,
557+
);
558+
discord_thread = try std.Thread.spawn(.{}, struct {
559+
fn run(monitor: *discord.DiscordHealthMonitor) void {
560+
monitor.run();
561+
}
562+
}.run, .{&discord_monitor.?});
563+
log.info("Discord health monitor enabled", .{});
564+
}
565+
545566
server.startWithReload(&shutdown_requested, &reload_requested, reloadConfigCallback) catch |err| {
546567
log.critical("Server error: {}", .{err});
547568
// Stop IMAP server on error
@@ -607,6 +628,12 @@ pub fn run(allocator: std.mem.Allocator, cli_args: args_parser.Args) !void {
607628
t.join();
608629
}
609630

631+
// Stop Discord health monitor
632+
if (discord_thread) |t| {
633+
t.join();
634+
log.info("Discord health monitor stopped", .{});
635+
}
636+
610637
// Cleanup
611638
if (cluster_manager) |cm| {
612639
cm.stop();

0 commit comments

Comments
 (0)