From f044d1ff6e0a65b5bcee6a8c99f7538a231d83c5 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Wed, 27 May 2026 13:53:49 +0200 Subject: [PATCH 1/6] feat: add anonymous installation ping to install scripts Send a best-effort installation ping after a successful install in both install.sh and install.ps1. The ping never aborts the install (3s timeout, errors swallowed) and is skipped when PIXI_NO_TELEMETRY or DO_NOT_TRACK is set. Co-Authored-By: Claude Opus 4.7 (1M context) --- install/install.ps1 | 11 +++++++++++ install/install.sh | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/install/install.ps1 b/install/install.ps1 index 9fd7fadde5..de3dbe80c6 100644 --- a/install/install.ps1 +++ b/install/install.ps1 @@ -211,6 +211,17 @@ try { Remove-Item -Path $ZIP_FILE } +# Send an anonymous installation ping (best-effort, never fails the install). +# Opt out by setting PIXI_NO_TELEMETRY or DO_NOT_TRACK. +if (-not $Env:PIXI_NO_TELEMETRY -and -not $Env:DO_NOT_TRACK) { + try { + $PingUrl = 'https://installation-ping.prefix.dev/a.png?x-pxid=21354c5b-2936-42bc-9d4b-9d6253815afd' + Invoke-WebRequest -Uri $PingUrl -UseBasicParsing -TimeoutSec 3 | Out-Null + } catch { + # Ignore telemetry errors + } +} + # Add pixi to PATH if the folder is not already in the PATH variable if (!$NoPathUpdate) { $PATH = Get-Env 'PATH' diff --git a/install/install.sh b/install/install.sh index 8ed02c90f6..41960dfac3 100644 --- a/install/install.sh +++ b/install/install.sh @@ -196,6 +196,17 @@ __wrap__() { echo "The 'pixi' binary is installed into '${PIXI_BIN_DIR}'" + # Send an anonymous installation ping (best-effort, never fails the install). + # Opt out by setting PIXI_NO_TELEMETRY or DO_NOT_TRACK. + if [ -z "${PIXI_NO_TELEMETRY:-}" ] && [ -z "${DO_NOT_TRACK:-}" ]; then + PING_URL="https://installation-ping.prefix.dev/a.png?x-pxid=21354c5b-2936-42bc-9d4b-9d6253815afd" + if hash curl 2>/dev/null; then + curl -sSL --max-time 3 "$PING_URL" >/dev/null 2>&1 || true + elif hash wget 2>/dev/null; then + wget -q -T 3 -O /dev/null "$PING_URL" >/dev/null 2>&1 || true + fi + fi + # shell update can be suppressed by `PIXI_NO_PATH_UPDATE` env var if [ -n "${PIXI_NO_PATH_UPDATE:-}" ]; then echo "No path update because PIXI_NO_PATH_UPDATE is set" From f7dac3119280b1d763c76e1be46860fd957aa02e Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Wed, 27 May 2026 13:54:43 +0200 Subject: [PATCH 2/6] docs: document installation ping opt-out Add PIXI_NO_TELEMETRY and DO_NOT_TRACK to the installer option tables and explain the anonymous installation ping and how to disable it. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/installation.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/installation.md b/docs/installation.md index 5088f66281..0233d1bea0 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -131,6 +131,8 @@ its [compile steps](https://github.com/conda/rattler/tree/main#give-it-a-try). | `PIXI_DOWNLOAD_URL` | Overrides the download URL for the Pixi binary (useful for mirrors or custom builds). | GitHub releases, e.g. [linux-64](https://github.com/prefix-dev/pixi/releases/latest/download/pixi-x86_64-unknown-linux-musl.tar.gz) | | `NETRC` | Path to a custom `.netrc` file for authentication with private repositories. | | | `TMP_DIR` | The temporary directory the script uses to download to and unpack the binary from. | `/tmp` | + | `PIXI_NO_TELEMETRY` | If set, the script will not send the anonymous installation ping (see note below). | | + | `DO_NOT_TRACK` | Same as `PIXI_NO_TELEMETRY`; honors the [Console Do Not Track](https://consoledonottrack.com/) standard. | | For example, on Apple Silicon, you can force the installation of the x86 version: ```shell @@ -185,6 +187,8 @@ its [compile steps](https://github.com/conda/rattler/tree/main#give-it-a-try). | `PIXI_HOME` | The location of the installation. | `$Env:USERPROFILE\.pixi` | | `PIXI_NO_PATH_UPDATE`| If set, the `$PATH` will not be updated to add `pixi` to it. | `false` | | `PIXI_DOWNLOAD_URL` | Overrides the download URL for the Pixi binary (useful for mirrors or custom builds). | GitHub releases, e.g. [win-64](https://github.com/prefix-dev/pixi/releases/latest/download/pixi-x86_64-pc-windows-msvc.zip) | + | `PIXI_NO_TELEMETRY` | If set, the script will not send the anonymous installation ping (see note below). | | + | `DO_NOT_TRACK` | Same as `PIXI_NO_TELEMETRY`; honors the [Console Do Not Track](https://consoledonottrack.com/) standard. | | For example, set the version: ```powershell @@ -202,6 +206,12 @@ its [compile steps](https://github.com/conda/rattler/tree/main#give-it-a-try). !!! tip "Security Note" The PowerShell install script automatically masks any credentials embedded in the download URL when displaying messages, replacing them with `***:***@` to prevent credentials from appearing in logs or console output. +!!! note "Anonymous installation ping" + After a successful install, the script sends a single anonymous request that helps us estimate how many installations happen. It contains no personal data and is best-effort: it has a short timeout and never blocks or fails the installation. To disable it, set `PIXI_NO_TELEMETRY=1` or `DO_NOT_TRACK=1` before running the script: + ```shell + curl -fsSL https://pixi.sh/install.sh | PIXI_NO_TELEMETRY=1 bash + ``` + ## Autocompletion To get autocompletion follow the instructions for your shell. From f247c7d70221fc94021669bb00f4b52633604cf9 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Wed, 27 May 2026 14:10:43 +0200 Subject: [PATCH 3/6] docs: drop link to external do-not-track site Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 0233d1bea0..c5df2bfc57 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -132,7 +132,7 @@ its [compile steps](https://github.com/conda/rattler/tree/main#give-it-a-try). | `NETRC` | Path to a custom `.netrc` file for authentication with private repositories. | | | `TMP_DIR` | The temporary directory the script uses to download to and unpack the binary from. | `/tmp` | | `PIXI_NO_TELEMETRY` | If set, the script will not send the anonymous installation ping (see note below). | | - | `DO_NOT_TRACK` | Same as `PIXI_NO_TELEMETRY`; honors the [Console Do Not Track](https://consoledonottrack.com/) standard. | | + | `DO_NOT_TRACK` | Same as `PIXI_NO_TELEMETRY`; honors the common `DO_NOT_TRACK` convention. | | For example, on Apple Silicon, you can force the installation of the x86 version: ```shell @@ -188,7 +188,7 @@ its [compile steps](https://github.com/conda/rattler/tree/main#give-it-a-try). | `PIXI_NO_PATH_UPDATE`| If set, the `$PATH` will not be updated to add `pixi` to it. | `false` | | `PIXI_DOWNLOAD_URL` | Overrides the download URL for the Pixi binary (useful for mirrors or custom builds). | GitHub releases, e.g. [win-64](https://github.com/prefix-dev/pixi/releases/latest/download/pixi-x86_64-pc-windows-msvc.zip) | | `PIXI_NO_TELEMETRY` | If set, the script will not send the anonymous installation ping (see note below). | | - | `DO_NOT_TRACK` | Same as `PIXI_NO_TELEMETRY`; honors the [Console Do Not Track](https://consoledonottrack.com/) standard. | | + | `DO_NOT_TRACK` | Same as `PIXI_NO_TELEMETRY`; honors the common `DO_NOT_TRACK` convention. | | For example, set the version: ```powershell From d324608973da047d03945c3c522ac32607540a9d Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Wed, 27 May 2026 14:30:23 +0200 Subject: [PATCH 4/6] feat: send anonymous ping after self-update After a successful `pixi self-update`, send a best-effort anonymous ping tagging os, arch and target version. It uses a 3s timeout and ignores all errors so it can never affect the update result, and is skipped when PIXI_NO_TELEMETRY or DO_NOT_TRACK is set. Reuses the same pixel id as the install scripts. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/pixi_cli/src/self_update.rs | 37 ++++++++++++++++++++++++++++++ crates/pixi_consts/src/consts.rs | 5 ++++ 2 files changed, 42 insertions(+) diff --git a/crates/pixi_cli/src/self_update.rs b/crates/pixi_cli/src/self_update.rs index 741043d86e..82956ab468 100644 --- a/crates/pixi_cli/src/self_update.rs +++ b/crates/pixi_cli/src/self_update.rs @@ -58,6 +58,40 @@ fn user_agent() -> String { format!("pixi {}", consts::PIXI_VERSION) } +/// Send a best-effort anonymous ping after a successful self-update, tagging +/// the OS, architecture and target version. It never blocks or fails the +/// update (short timeout, all errors ignored) and is skipped when +/// `PIXI_NO_TELEMETRY` or `DO_NOT_TRACK` is set. +async fn send_update_ping(target_version: Option<&Version>) { + if std::env::var_os("PIXI_NO_TELEMETRY").is_some() || std::env::var_os("DO_NOT_TRACK").is_some() + { + return; + } + + let version = target_version + .map(|v| v.to_string()) + .unwrap_or_else(|| "latest".to_string()); + + let url = format!( + "{}?x-pxid={}&event=self-update&os={}&arch={}&version={}", + consts::INSTALL_PING_URL, + consts::INSTALL_PING_PXID, + std::env::consts::OS, + std::env::consts::ARCH, + version, + ); + + let Ok((_, client)) = build_reqwest_clients(None, None) else { + return; + }; + let _ = client + .get(url) + .header("User-Agent", user_agent()) + .timeout(std::time::Duration::from_secs(3)) + .send() + .await; +} + fn default_archive_name() -> Option { if cfg!(target_os = "macos") { if cfg!(target_arch = "x86_64") { @@ -410,6 +444,9 @@ pub async fn execute(args: Args, global_options: &GlobalOptions) -> miette::Resu tracing::warn!(fetch_release_warning); } + // Best-effort anonymous ping; must not affect the update result. + send_update_ping(target_version.as_ref()).await; + Ok(()) } diff --git a/crates/pixi_consts/src/consts.rs b/crates/pixi_consts/src/consts.rs index 7b36abf699..76a6cf7da4 100644 --- a/crates/pixi_consts/src/consts.rs +++ b/crates/pixi_consts/src/consts.rs @@ -124,6 +124,11 @@ pub const RELEASES_API_BY_TAG: &str = "https://api.github.com/repos/prefix-dev/p pub const RELEASES_API_LATEST: &str = "https://api.github.com/repos/prefix-dev/pixi/releases/latest"; +/// Endpoint for the anonymous installation/update ping. The same pixel id is +/// used by the install scripts (`install/install.sh`, `install/install.ps1`). +pub const INSTALL_PING_URL: &str = "https://installation-ping.prefix.dev/a.png"; +pub const INSTALL_PING_PXID: &str = "21354c5b-2936-42bc-9d4b-9d6253815afd"; + pub const CLAP_CONFIG_OPTIONS: &str = "Config Options"; pub const CLAP_GIT_OPTIONS: &str = "Git Options"; pub const CLAP_GLOBAL_OPTIONS: &str = "Global Options"; From 91531b6525bf9889c2ae83a98b70cb13a5da3d74 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Wed, 27 May 2026 14:38:50 +0200 Subject: [PATCH 5/6] feat: encode ping metadata into Scarf Page parameter Instead of arbitrary os/arch query params (which Scarf only reports with extra org config), encode event/version/platform into a synthetic page URL passed via the `Page` parameter. Scarf reports on the page dimension, so each combination (e.g. /ping/self-update/0.69.0/linux-x86_64) shows up as its own page without any dashboard setup. Applied to the self-update ping and both install scripts. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/pixi_cli/src/self_update.rs | 17 +++++++++++------ install/install.ps1 | 7 ++++++- install/install.sh | 11 ++++++++++- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/crates/pixi_cli/src/self_update.rs b/crates/pixi_cli/src/self_update.rs index 82956ab468..0553bfcfc5 100644 --- a/crates/pixi_cli/src/self_update.rs +++ b/crates/pixi_cli/src/self_update.rs @@ -72,20 +72,25 @@ async fn send_update_ping(target_version: Option<&Version>) { .map(|v| v.to_string()) .unwrap_or_else(|| "latest".to_string()); - let url = format!( - "{}?x-pxid={}&event=self-update&os={}&arch={}&version={}", - consts::INSTALL_PING_URL, - consts::INSTALL_PING_PXID, + // Encode the metadata as a synthetic page URL. Scarf reports on the `Page` + // dimension (normally inferred from the referrer), so each event/version/ + // platform combination shows up as its own page in the dashboard. + let page = format!( + "https://pixi.sh/ping/self-update/{}/{}-{}", + version, std::env::consts::OS, std::env::consts::ARCH, - version, ); let Ok((_, client)) = build_reqwest_clients(None, None) else { return; }; let _ = client - .get(url) + .get(consts::INSTALL_PING_URL) + .query(&[ + ("x-pxid", consts::INSTALL_PING_PXID), + ("Page", page.as_str()), + ]) .header("User-Agent", user_agent()) .timeout(std::time::Duration::from_secs(3)) .send() diff --git a/install/install.ps1 b/install/install.ps1 index de3dbe80c6..81f64ff585 100644 --- a/install/install.ps1 +++ b/install/install.ps1 @@ -215,7 +215,12 @@ try { # Opt out by setting PIXI_NO_TELEMETRY or DO_NOT_TRACK. if (-not $Env:PIXI_NO_TELEMETRY -and -not $Env:DO_NOT_TRACK) { try { - $PingUrl = 'https://installation-ping.prefix.dev/a.png?x-pxid=21354c5b-2936-42bc-9d4b-9d6253815afd' + $PingArch = if ($ARCH -like 'aarch64*') { 'aarch64' } elseif ($ARCH -like 'i686*') { 'i686' } else { 'x86_64' } + # Metadata is encoded into the `Page` query parameter so it shows up as a + # distinct page in Scarf. Invoke-WebRequest URL-encodes the parameter value. + $Page = "https://pixi.sh/ping/install/$PixiVersion/windows-$PingArch" + $PingBase = 'https://installation-ping.prefix.dev/a.png' + $PingUrl = "$PingBase`?x-pxid=21354c5b-2936-42bc-9d4b-9d6253815afd&Page=$([uri]::EscapeDataString($Page))" Invoke-WebRequest -Uri $PingUrl -UseBasicParsing -TimeoutSec 3 | Out-Null } catch { # Ignore telemetry errors diff --git a/install/install.sh b/install/install.sh index 41960dfac3..a8e4bc9a51 100644 --- a/install/install.sh +++ b/install/install.sh @@ -199,7 +199,16 @@ __wrap__() { # Send an anonymous installation ping (best-effort, never fails the install). # Opt out by setting PIXI_NO_TELEMETRY or DO_NOT_TRACK. if [ -z "${PIXI_NO_TELEMETRY:-}" ] && [ -z "${DO_NOT_TRACK:-}" ]; then - PING_URL="https://installation-ping.prefix.dev/a.png?x-pxid=21354c5b-2936-42bc-9d4b-9d6253815afd" + case "$PLATFORM" in + apple-darwin) OS_TAG="macos" ;; + *linux*) OS_TAG="linux" ;; + *windows*) OS_TAG="windows" ;; + *) OS_TAG="unknown" ;; + esac + # Metadata is encoded into the (pre-encoded) `Page` query parameter so it + # shows up as a distinct page in Scarf; '%2F' are the path separators. + PAGE="https%3A%2F%2Fpixi.sh%2Fping%2Finstall%2F${VERSION}%2F${OS_TAG}-${ARCH}" + PING_URL="https://installation-ping.prefix.dev/a.png?x-pxid=21354c5b-2936-42bc-9d4b-9d6253815afd&Page=${PAGE}" if hash curl 2>/dev/null; then curl -sSL --max-time 3 "$PING_URL" >/dev/null 2>&1 || true elif hash wget 2>/dev/null; then From a0fbb358d6160ebfb6bf08f1074fc97ab2cfa1cc Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Thu, 28 May 2026 08:19:32 +0200 Subject: [PATCH 6/6] reuse client --- crates/pixi_cli/src/self_update.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/pixi_cli/src/self_update.rs b/crates/pixi_cli/src/self_update.rs index 0553bfcfc5..0a9c1d0eb9 100644 --- a/crates/pixi_cli/src/self_update.rs +++ b/crates/pixi_cli/src/self_update.rs @@ -62,7 +62,10 @@ fn user_agent() -> String { /// the OS, architecture and target version. It never blocks or fails the /// update (short timeout, all errors ignored) and is skipped when /// `PIXI_NO_TELEMETRY` or `DO_NOT_TRACK` is set. -async fn send_update_ping(target_version: Option<&Version>) { +async fn send_update_ping( + client: &reqwest_middleware::ClientWithMiddleware, + target_version: Option<&Version>, +) { if std::env::var_os("PIXI_NO_TELEMETRY").is_some() || std::env::var_os("DO_NOT_TRACK").is_some() { return; @@ -82,9 +85,6 @@ async fn send_update_ping(target_version: Option<&Version>) { std::env::consts::ARCH, ); - let Ok((_, client)) = build_reqwest_clients(None, None) else { - return; - }; let _ = client .get(consts::INSTALL_PING_URL) .query(&[ @@ -450,7 +450,7 @@ pub async fn execute(args: Args, global_options: &GlobalOptions) -> miette::Resu } // Best-effort anonymous ping; must not affect the update result. - send_update_ping(target_version.as_ref()).await; + send_update_ping(&client, target_version.as_ref()).await; Ok(()) }