cli: implement better self-updating

- Start separating a "standalone" CLI. This is a little awkward with clap-
  derive, but I got it working. Detection of whether the CLI _is_
  standalone is still todo.
- Remove the old ad-hoc update code for code-server, and use the update
  service instead.
- Fix some of the "permission denied" errors people got while updating
  before. We need to rename the old running binary, not just overwrite it.
This commit is contained in:
Connor Peet
2022-09-23 17:44:22 -07:00
parent a9bcb15b75
commit 07453efc00
22 changed files with 381 additions and 102 deletions

View File

@@ -6,15 +6,15 @@
use std::collections::HashMap;
use cli::commands::args::{
Cli, Commands, DesktopCodeOptions, ExtensionArgs, ExtensionSubcommand, InstallExtensionArgs,
ListExtensionArgs, UninstallExtensionArgs,
CliCore, Commands, DesktopCodeOptions, ExtensionArgs, ExtensionSubcommand,
InstallExtensionArgs, ListExtensionArgs, UninstallExtensionArgs,
};
/// Tries to parse the argv using the legacy CLI interface, looking for its
/// flags and generating a CLI with subcommands if those don't exist.
pub fn try_parse_legacy(
iter: impl IntoIterator<Item = impl Into<std::ffi::OsString>>,
) -> Option<Cli> {
) -> Option<CliCore> {
let raw = clap_lex::RawArgs::new(iter);
let mut cursor = raw.cursor();
raw.next(&mut cursor); // Skip the bin
@@ -65,7 +65,7 @@ pub fn try_parse_legacy(
// --status -> status
if args.contains_key("list-extensions") {
Some(Cli {
Some(CliCore {
subcommand: Some(Commands::Extension(ExtensionArgs {
subcommand: ExtensionSubcommand::List(ListExtensionArgs {
category: get_first_arg_value("category"),
@@ -76,7 +76,7 @@ pub fn try_parse_legacy(
..Default::default()
})
} else if let Some(exts) = args.remove("install-extension") {
Some(Cli {
Some(CliCore {
subcommand: Some(Commands::Extension(ExtensionArgs {
subcommand: ExtensionSubcommand::Install(InstallExtensionArgs {
id_or_path: exts,
@@ -88,7 +88,7 @@ pub fn try_parse_legacy(
..Default::default()
})
} else if let Some(exts) = args.remove("uninstall-extension") {
Some(Cli {
Some(CliCore {
subcommand: Some(Commands::Extension(ExtensionArgs {
subcommand: ExtensionSubcommand::Uninstall(UninstallExtensionArgs { id: exts }),
desktop_code_options,
@@ -96,7 +96,7 @@ pub fn try_parse_legacy(
..Default::default()
})
} else if args.contains_key("status") {
Some(Cli {
Some(CliCore {
subcommand: Some(Commands::Status),
..Default::default()
})

View File

@@ -8,7 +8,7 @@ use std::process::Command;
use clap::Parser;
use cli::{
commands::{args, tunnels, version, CommandContext},
commands::{args, tunnels, update, version, CommandContext},
desktop, log as own_log,
state::LauncherPaths,
update_service::UpdateService,
@@ -26,68 +26,82 @@ use log::{Level, Metadata, Record};
#[tokio::main]
async fn main() -> Result<(), std::convert::Infallible> {
let raw_args = std::env::args_os().collect::<Vec<_>>();
let parsed = try_parse_legacy(&raw_args).unwrap_or_else(|| args::Cli::parse_from(&raw_args));
// todo: only parse to the standalone CLI if not integrated
let parsed = try_parse_legacy(&raw_args)
.map(|core| args::AnyCli::Integrated(args::IntegratedCli { core }))
.unwrap_or_else(|| args::AnyCli::Standalone(args::StandaloneCli::parse_from(&raw_args)));
let core = parsed.core();
let context = CommandContext {
http: reqwest::Client::new(),
paths: LauncherPaths::new(&parsed.global_options.cli_data_dir).unwrap(),
paths: LauncherPaths::new(&core.global_options.cli_data_dir).unwrap(),
log: own_log::Logger::new(
SdkTracerProvider::builder().build().tracer("codecli"),
if parsed.global_options.verbose {
if core.global_options.verbose {
own_log::Level::Trace
} else {
parsed.global_options.log.unwrap_or(own_log::Level::Info)
core.global_options.log.unwrap_or(own_log::Level::Info)
},
),
args: parsed,
args: core.clone(),
};
log::set_logger(Box::leak(Box::new(RustyLogger(context.log.clone()))))
.map(|()| log::set_max_level(log::LevelFilter::Debug))
.expect("expected to make logger");
let result = match context.args.subcommand.clone() {
None => {
let ca = context.args.get_base_code_args();
start_code(context, ca).await
}
Some(args::Commands::Extension(extension_args)) => {
let mut ca = context.args.get_base_code_args();
extension_args.add_code_args(&mut ca);
start_code(context, ca).await
}
Some(args::Commands::Status) => {
let mut ca = context.args.get_base_code_args();
ca.push("--status".to_string());
start_code(context, ca).await
}
Some(args::Commands::Version(version_args)) => match version_args.subcommand {
args::VersionSubcommand::Use(use_version_args) => {
version::switch_to(context, use_version_args).await
}
args::VersionSubcommand::Uninstall(uninstall_version_args) => {
version::uninstall(context, uninstall_version_args).await
}
args::VersionSubcommand::List(list_version_args) => {
version::list(context, list_version_args).await
}
let result = match parsed {
args::AnyCli::Standalone(args::StandaloneCli {
subcommand: Some(cmd),
..
}) => match cmd {
args::StandaloneCommands::Update(args) => update::update(context, args).await,
},
args::AnyCli::Standalone(args::StandaloneCli { core: c, .. })
| args::AnyCli::Integrated(args::IntegratedCli { core: c, .. }) => match c.subcommand {
None => {
let ca = context.args.get_base_code_args();
start_code(context, ca).await
}
Some(args::Commands::Tunnel(tunnel_args)) => match tunnel_args.subcommand {
Some(args::TunnelSubcommand::Prune) => tunnels::prune(context).await,
Some(args::TunnelSubcommand::Unregister) => tunnels::unregister(context).await,
Some(args::TunnelSubcommand::Rename(rename_args)) => {
tunnels::rename(context, rename_args).await
Some(args::Commands::Extension(extension_args)) => {
let mut ca = context.args.get_base_code_args();
extension_args.add_code_args(&mut ca);
start_code(context, ca).await
}
Some(args::TunnelSubcommand::User(user_command)) => {
tunnels::user(context, user_command).await
Some(args::Commands::Status) => {
let mut ca = context.args.get_base_code_args();
ca.push("--status".to_string());
start_code(context, ca).await
}
Some(args::TunnelSubcommand::Service(service_args)) => {
tunnels::service(context, service_args).await
}
None => tunnels::serve(context, tunnel_args.serve_args).await,
Some(args::Commands::Version(version_args)) => match version_args.subcommand {
args::VersionSubcommand::Use(use_version_args) => {
version::switch_to(context, use_version_args).await
}
args::VersionSubcommand::Uninstall(uninstall_version_args) => {
version::uninstall(context, uninstall_version_args).await
}
args::VersionSubcommand::List(list_version_args) => {
version::list(context, list_version_args).await
}
},
Some(args::Commands::Tunnel(tunnel_args)) => match tunnel_args.subcommand {
Some(args::TunnelSubcommand::Prune) => tunnels::prune(context).await,
Some(args::TunnelSubcommand::Unregister) => tunnels::unregister(context).await,
Some(args::TunnelSubcommand::Rename(rename_args)) => {
tunnels::rename(context, rename_args).await
}
Some(args::TunnelSubcommand::User(user_command)) => {
tunnels::user(context, user_command).await
}
Some(args::TunnelSubcommand::Service(service_args)) => {
tunnels::service(context, service_args).await
}
None => tunnels::serve(context, tunnel_args.serve_args).await,
},
},
};