mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-07-31 09:46:05 -04:00
Merge pull request #2816 from lightpanda-io/nikneym/check-update
initial implementation for `update` command
This commit is contained in:
@@ -262,7 +262,9 @@ const Commands = cli.Builder(.{
|
||||
},
|
||||
.shared_options = CommonOptions,
|
||||
},
|
||||
.{ .name = "version", .options = .{} },
|
||||
.{ .name = "version", .options = .{
|
||||
.{ .name = "check", .type = bool },
|
||||
} },
|
||||
});
|
||||
|
||||
pub const RunMode = Commands.Enum;
|
||||
@@ -274,7 +276,11 @@ exec_name: []const u8,
|
||||
http_headers: HttpHeaders,
|
||||
|
||||
fn modeNeedsHttp(mode: Mode) bool {
|
||||
return mode != .help and mode != .version;
|
||||
return switch (mode) {
|
||||
.help => false,
|
||||
.version => |opts| opts.check,
|
||||
else => true,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(allocator: Allocator, exec_name: []const u8, mode: Mode) !Config {
|
||||
@@ -307,6 +313,8 @@ pub fn interactive(self: *const Config) bool {
|
||||
pub fn tlsVerifyHost(self: *const Config) bool {
|
||||
return switch (self.mode) {
|
||||
inline .serve, .fetch, .mcp, .agent => |opts| !opts.insecure_disable_tls_host_verification,
|
||||
// `version --check` talks to the release endpoint; always verify.
|
||||
.version => true,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
@@ -356,6 +364,7 @@ pub fn v8MaxHeapMb(self: *const Config) ?u32 {
|
||||
pub fn httpProxy(self: *const Config) ?[:0]const u8 {
|
||||
return switch (self.mode) {
|
||||
inline .serve, .fetch, .mcp, .agent => |opts| opts.http_proxy,
|
||||
.version => null,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
@@ -363,7 +372,7 @@ pub fn httpProxy(self: *const Config) ?[:0]const u8 {
|
||||
pub fn proxyBearerToken(self: *const Config) ?[:0]const u8 {
|
||||
return switch (self.mode) {
|
||||
inline .serve, .fetch, .mcp, .agent => |opts| opts.proxy_bearer_token,
|
||||
.help, .version => null,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -384,6 +393,7 @@ pub fn httpMaxHostOpen(self: *const Config) u8 {
|
||||
pub fn httpConnectTimeout(self: *const Config) u31 {
|
||||
return switch (self.mode) {
|
||||
inline .serve, .fetch, .mcp, .agent => |opts| opts.http_connect_timeout orelse 0,
|
||||
.version => 0,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
@@ -391,6 +401,7 @@ pub fn httpConnectTimeout(self: *const Config) u31 {
|
||||
pub fn httpTimeout(self: *const Config) u31 {
|
||||
return switch (self.mode) {
|
||||
inline .serve, .fetch, .mcp, .agent => |opts| opts.http_timeout orelse 5000,
|
||||
.version => 5000,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
@@ -465,14 +476,14 @@ pub fn logFilterScopes(self: *const Config) std.ArrayList(log.FilterRule) {
|
||||
pub fn userAgentSuffix(self: *const Config) ?[]const u8 {
|
||||
return switch (self.mode) {
|
||||
inline .serve, .fetch, .mcp, .agent => |opts| opts.user_agent_suffix,
|
||||
.help, .version => null,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn userAgent(self: *const Config) ?[]const u8 {
|
||||
return switch (self.mode) {
|
||||
inline .serve, .fetch, .mcp, .agent => |opts| opts.user_agent,
|
||||
.help, .version => null,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -520,7 +531,7 @@ pub fn webBotAuth(self: *const Config) ?WebBotAuthConfig {
|
||||
.keyid = opts.web_bot_auth_keyid orelse return null,
|
||||
.domain = opts.web_bot_auth_domain orelse return null,
|
||||
},
|
||||
.help, .version => null,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
101
src/Updater.zig
Normal file
101
src/Updater.zig
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const json = std.json;
|
||||
const SemanticVersion = std.SemanticVersion;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const Network = @import("network/Network.zig");
|
||||
const http = @import("network/http.zig");
|
||||
const libcurl = @import("sys/libcurl.zig");
|
||||
const crypto = @import("sys/libcrypto.zig");
|
||||
const Config = @import("Config.zig");
|
||||
const log = @import("log.zig");
|
||||
|
||||
/// Sole purpose of this client is to do updates; hence, its very minimal.
|
||||
const Updater = @This();
|
||||
x509_store: *crypto.X509_STORE,
|
||||
config: *const Config,
|
||||
|
||||
/// Initializes the update client; meant to be used as singleton.
|
||||
pub fn init(allocator: Allocator, config: *const Config) !Updater {
|
||||
Network.globalInit(allocator);
|
||||
errdefer Network.globalDeinit();
|
||||
const x509_store = try Network.createX509Store(allocator);
|
||||
|
||||
return .{
|
||||
.x509_store = x509_store,
|
||||
.config = config,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Updater) void {
|
||||
Network.globalDeinit();
|
||||
crypto.X509_STORE_free(self.x509_store);
|
||||
}
|
||||
|
||||
/// Sends running Lightpanda version to remote to get update information.
|
||||
/// Outputs directly to given `Writer`.
|
||||
pub fn inform(self: *Updater, writer: *std.Io.Writer) !void {
|
||||
const conn = try http.Connection.init(self.x509_store, self.config, null);
|
||||
defer conn.deinit();
|
||||
|
||||
const url = std.fmt.comptimePrint("https://telemetry.lightpanda.io/v/{s}", .{lp.build_config.version});
|
||||
// Prepare the request.
|
||||
try conn.setURL(url);
|
||||
try conn.setGetMode();
|
||||
try conn.setFollowLocation(true);
|
||||
|
||||
// Wraps everything needed to receive bytes.
|
||||
const ReceiverContext = struct {
|
||||
writer: *std.Io.Writer,
|
||||
err: std.Io.Writer.Error!void = {},
|
||||
|
||||
/// curl -> writer.
|
||||
fn drain(
|
||||
buffer: [*]const u8,
|
||||
buf_count: usize,
|
||||
buf_len: usize,
|
||||
raw_ctx: *anyopaque,
|
||||
) callconv(.c) usize {
|
||||
const ctx: *@This() = @ptrCast(@alignCast(raw_ctx));
|
||||
const chunk = buffer[0 .. buf_count * buf_len];
|
||||
ctx.writer.writeAll(chunk) catch |err| {
|
||||
ctx.err = err;
|
||||
return 0;
|
||||
};
|
||||
|
||||
return chunk.len;
|
||||
}
|
||||
};
|
||||
|
||||
// Set receiver context.
|
||||
var ctx = ReceiverContext{ .writer = writer };
|
||||
try libcurl.curl_easy_setopt(conn._easy, .write_data, &ctx);
|
||||
try libcurl.curl_easy_setopt(conn._easy, .write_function, ReceiverContext.drain);
|
||||
|
||||
// Make a request.
|
||||
const status_int = conn.perform() catch |err| {
|
||||
ctx.err catch |ctx_err| return ctx_err;
|
||||
return err;
|
||||
};
|
||||
_ = status_int;
|
||||
return writer.flush();
|
||||
}
|
||||
@@ -238,9 +238,15 @@
|
||||
\\API key.
|
||||
,
|
||||
.version =
|
||||
\\usage: {0s} version
|
||||
\\usage: {0s} version [OPTIONS]
|
||||
\\
|
||||
\\Displays the version of {0s}.
|
||||
\\
|
||||
\\options:
|
||||
\\ --check
|
||||
\\ Checks whether a newer release of {0s} is available. When one is
|
||||
\\ found, prints the running version, the latest release, and how to
|
||||
\\ update.
|
||||
,
|
||||
.help =
|
||||
\\usage: {0s} help
|
||||
|
||||
@@ -56,6 +56,8 @@ pub const build_config = @import("build_config");
|
||||
pub const crash_handler = @import("crash_handler.zig");
|
||||
pub const core_dump = @import("core_dump.zig");
|
||||
|
||||
pub const Updater = @import("Updater.zig");
|
||||
|
||||
pub const FetchOpts = struct {
|
||||
wait_ms: u32 = 5000,
|
||||
wait_until: ?Config.WaitUntil = null,
|
||||
@@ -223,6 +225,17 @@ fn dumpContent(app: *App, mode: Config.DumpFormat, dump_opts: dump.Opts, frame:
|
||||
}
|
||||
}
|
||||
|
||||
pub fn checkVersion(allocator: std.mem.Allocator, config: *const Config) !void {
|
||||
var client = try Updater.init(allocator, config);
|
||||
defer client.deinit();
|
||||
|
||||
var stdout = std.fs.File.stdout();
|
||||
var buf: [4096]u8 = undefined;
|
||||
var writer = stdout.writer(&buf);
|
||||
const w = &writer.interface;
|
||||
try client.inform(w);
|
||||
}
|
||||
|
||||
// Writes a single page's result object. Framing (the enclosing array and any
|
||||
// separators / trailing newline) is the caller's responsibility.
|
||||
fn writeJsonEnvelope(writer: *std.Io.Writer, frame: ?*Frame, dump_mode: ?Config.DumpFormat, content: []const u8) !void {
|
||||
|
||||
10
src/main.zig
10
src/main.zig
@@ -71,9 +71,13 @@ fn run(allocator: Allocator, main_arena: Allocator) !void {
|
||||
|
||||
switch (args.mode) {
|
||||
.help => |tag| return args.printUsageAndExit(tag, true),
|
||||
.version => {
|
||||
var stdout = std.fs.File.stdout().writer(&.{});
|
||||
try stdout.interface.print("{s}\n", .{lp.build_config.version});
|
||||
.version => |opts| {
|
||||
if (opts.check) {
|
||||
try lp.checkVersion(allocator, &args);
|
||||
} else {
|
||||
var stdout = std.fs.File.stdout().writer(&.{});
|
||||
try stdout.interface.print("{s}\n", .{lp.build_config.version});
|
||||
}
|
||||
return std.process.cleanExit();
|
||||
},
|
||||
.agent => |opts| if (opts.list_models) {
|
||||
|
||||
@@ -138,7 +138,9 @@ cdp_start: usize,
|
||||
/// Optional IP filter for blocking requests to private/internal networks (--block-private-networks).
|
||||
ip_filter: ?*IpFilter = null,
|
||||
|
||||
fn globalInit(allocator: Allocator) void {
|
||||
/// Calling `init` also calls this function; only marked public for situations
|
||||
/// networking is needed without `App`.
|
||||
pub fn globalInit(allocator: Allocator) void {
|
||||
// Only route curl's own allocations through our allocator in Debug, so the
|
||||
// leak detector sees them. In Release it'd just wrap c_allocator (curl's
|
||||
// default malloc anyway) at the cost of a per-allocation header.
|
||||
@@ -152,7 +154,9 @@ fn globalInit(allocator: Allocator) void {
|
||||
};
|
||||
}
|
||||
|
||||
fn globalDeinit() void {
|
||||
/// Calling `deinit` also calls this function; only marked public for situations
|
||||
/// networking is needed without `App`.
|
||||
pub fn globalDeinit() void {
|
||||
libcurl.curl_global_cleanup();
|
||||
}
|
||||
|
||||
@@ -720,7 +724,7 @@ const CreateX509StoreError = std.crypto.Certificate.Bundle.RescanError || error{
|
||||
|
||||
/// NEVER give full ownership of store to `SSL_CTX`, always rely on ref counting.
|
||||
/// Allocations made through passed `allocator` are freed before this function returns.
|
||||
fn createX509Store(allocator: Allocator) CreateX509StoreError!*crypto.X509_STORE {
|
||||
pub fn createX509Store(allocator: Allocator) CreateX509StoreError!*crypto.X509_STORE {
|
||||
const store = crypto.X509_STORE_new() orelse return error.FailedToCreateX509Store;
|
||||
errdefer crypto.X509_STORE_free(store);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user