AI: download geoip on startup if it does not exist

This commit is contained in:
Emil Lerch 2025-12-18 11:23:38 -08:00
parent 3a7d65eeb7
commit 58d09b1f7e
Signed by: lobo
GPG key ID: A7B62D657EF764F8
2 changed files with 76 additions and 0 deletions

View file

@ -0,0 +1,72 @@
const std = @import("std");
pub fn ensureDatabase(allocator: std.mem.Allocator, path: []const u8) !void {
std.fs.cwd().access(path, .{}) catch {
std.log.info("GeoLite2 database not found at {s}, downloading...", .{path});
try downloadDatabase(allocator, path);
std.log.info("GeoLite2 database downloaded successfully", .{});
return;
};
}
fn downloadDatabase(allocator: std.mem.Allocator, path: []const u8) !void {
const latest_url = try getLatestReleaseUrl(allocator);
defer allocator.free(latest_url);
var client: std.http.Client = .{ .allocator = allocator };
defer client.deinit();
const uri = try std.Uri.parse(latest_url);
const response_buf = try allocator.alloc(u8, 64 * 1024 * 1024);
defer allocator.free(response_buf);
var writer = std.Io.Writer.fixed(response_buf);
const result = try client.fetch(.{
.location = .{ .uri = uri },
.method = .GET,
.response_writer = &writer,
});
if (result.status != .ok) return error.DownloadFailed;
const file = try std.fs.cwd().createFile(path, .{});
defer file.close();
try file.writeAll(response_buf[0..writer.end]);
}
fn getLatestReleaseUrl(allocator: std.mem.Allocator) ![]const u8 {
var client: std.http.Client = .{ .allocator = allocator };
defer client.deinit();
const api_url = "https://api.github.com/repos/P3TERX/GeoLite.mmdb/releases/latest";
const uri = try std.Uri.parse(api_url);
const response_buf = try allocator.alloc(u8, 1024 * 1024);
defer allocator.free(response_buf);
var writer = std.Io.Writer.fixed(response_buf);
const result = try client.fetch(.{
.location = .{ .uri = uri },
.method = .GET,
.response_writer = &writer,
.extra_headers = &.{
.{ .name = "User-Agent", .value = "wttr.in" },
},
});
if (result.status != .ok) return error.ApiFailed;
const json_data = response_buf[0..writer.end];
const parsed = try std.json.parseFromSlice(std.json.Value, allocator, json_data, .{});
defer parsed.deinit();
const assets = parsed.value.object.get("assets") orelse return error.NoAssets;
for (assets.array.items) |asset| {
const name = asset.object.get("name") orelse continue;
if (std.mem.eql(u8, name.string, "GeoLite2-City.mmdb")) {
const url = asset.object.get("browser_download_url") orelse return error.NoDownloadUrl;
return allocator.dupe(u8, url.string);
}
}
return error.DatabaseNotFound;
}

View file

@ -9,6 +9,7 @@ const GeoIP = @import("location/geoip.zig").GeoIP;
const GeoCache = @import("location/geocache.zig").GeoCache;
const AirportDB = @import("location/airports.zig").AirportDB;
const Resolver = @import("location/resolver.zig").Resolver;
const geolite_downloader = @import("location/geolite_downloader.zig");
pub const std_options: std.Options = .{
.log_level = .info,
@ -40,6 +41,9 @@ pub fn main() !void {
}
try stdout.flush();
// Ensure GeoLite2 database exists
try geolite_downloader.ensureDatabase(allocator, cfg.geolite_path);
// Initialize GeoIP database
var geoip = GeoIP.init(cfg.geolite_path) catch |err| {
std.log.warn("Failed to load GeoIP database: {}", .{err});