Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ Query params take precedence when both styles are combined.
### Linux / macOS

```shell
curl -s -o- https://raw.githubusercontent.com/ViGrise/previewproxy/main/install.sh | sudo bash
curl -s -o- https://raw.githubusercontent.com/ViGrise/previewproxy/main/install.sh | sudo -E bash
```

```shell
wget -qO- https://raw.githubusercontent.com/ViGrise/previewproxy/main/install.sh | sudo bash
wget -qO- https://raw.githubusercontent.com/ViGrise/previewproxy/main/install.sh | sudo -E bash
```

Installs to `/usr/local/bin`. Override with env vars:
Expand Down
53 changes: 53 additions & 0 deletions src/modules/cli/subcommands/upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,46 @@ pub fn download_url(tag: &str) -> String {
)
}

/// Re-execute the current process with elevated privileges.
///
/// On Unix this execs `sudo <exe> upgrade`, replacing the current process.
/// On Windows this uses PowerShell RunAs, waits for the child,
/// then the caller should exit immediately.
fn reexec_with_privileges(exe: &std::path::Path) -> Result<()> {
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
let err = std::process::Command::new("sudo")
.arg("--")
.arg(exe)
.arg("upgrade")
.exec();
anyhow::bail!("Failed to re-exec with sudo: {err}");
}

#[cfg(windows)]
{
let exe_str = exe.to_string_lossy();
let ps_cmd = format!(
"Start-Process -FilePath '{}' -ArgumentList 'upgrade' -Verb RunAs -Wait",
exe_str.replace("'", "\'")
);
let status = std::process::Command::new("powershell")
.args(["-Command", &ps_cmd])
.status()
.map_err(|e| anyhow::anyhow!("Failed to re-launch with elevated permissions: {e}"))?;
if !status.success() {
anyhow::bail!("Elevated upgrade process failed");
}
Ok(())
}

#[cfg(not(any(unix, windows)))]
{
anyhow::bail!("Cannot write to the binary directory. Re-run with elevated permissions.");
}
}

pub async fn run_upgrade() -> Result<()> {
use std::io::Write;
use std::time::Duration;
Expand All @@ -58,6 +98,19 @@ pub async fn run_upgrade() -> Result<()> {
}
}

// Fail fast before downloading if we lack write permission on the exe directory.
// If so, re-exec with elevated privileges rather than returning an error.
if tempfile::Builder::new()
.prefix(".previewproxy-upgrade-check-")
.tempfile_in(exe_dir)
.is_err()
{
reexec_with_privileges(&exe)?;
// reexec_with_privileges replaces this process on Unix; on Windows it
// waits for the elevated process to finish and then exits.
std::process::exit(0);
}

let client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.user_agent("previewproxy-updater")
Expand Down