diff --git a/.pipelines/CosmosDB-Shell-Official.yml b/.pipelines/CosmosDB-Shell-Official.yml
index 3432ffc3..2658090e 100644
--- a/.pipelines/CosmosDB-Shell-Official.yml
+++ b/.pipelines/CosmosDB-Shell-Official.yml
@@ -31,6 +31,10 @@ variables:
BuildConfiguration: Release
WindowsContainerImage: "onebranch.azurecr.io/windows/ltsc2022/vse2022:latest" # Docker image which is used to build the project https://aka.ms/obpipelines/containers
+ # Azure Linux is RPM-native and resolves packages from packages.microsoft.com.
+ # The build containers cannot reach the public Ubuntu archives, so an
+ # Ubuntu image cannot install the RPM tooling the rpm job needs.
+ LinuxContainerImage: "mcr.microsoft.com/onebranch/azurelinux/build:3.0"
resources:
repositories:
@@ -822,6 +826,122 @@ extends:
publishVstsFeed: "CosmosDB/CosmosDBShell"
allowPackageConflicts: true
+ - job: rpm
+ displayName: Build RPM packages
+ pool:
+ type: linux
+ variables:
+ ob_outputDirectory: "$(Build.SourcesDirectory)/out"
+ ob_artifactBaseName: cosmos_shell_rpm
+ ob_git_fetchDepth: -1
+ steps:
+ - task: UseDotNet@2
+ inputs:
+ packageType: "sdk"
+ useGlobalJson: true
+ performMultiLevelLookup: true
+
+ - script: |
+ set -euo pipefail
+ dotnet tool restore --configfile "$(Build.SourcesDirectory)/.pipelines/nuget.config"
+ package_version="$(dotnet tool run nbgv get-version -v NuGetPackageVersion)"
+ package_version="${package_version%%+*}"
+ if [[ ! "$package_version" =~ ^([0-9]+\.[0-9]+\.[0-9]+)(-(.+))?$ ]]; then
+ echo "Unsupported RPM package version: $package_version" >&2
+ exit 1
+ fi
+
+ echo "##vso[task.setvariable variable=CosmosDBShell_RpmVersion]${BASH_REMATCH[1]}"
+ if [[ -n "${BASH_REMATCH[3]:-}" ]]; then
+ rpm_release="0.${BASH_REMATCH[3]//[^[:alnum:].]/.}"
+ else
+ rpm_release="1"
+ fi
+ echo "##vso[task.setvariable variable=CosmosDBShell_RpmRelease]$rpm_release"
+ displayName: Compute RPM version
+
+ - script: |
+ set -euo pipefail
+ if ! command -v rpmbuild >/dev/null 2>&1; then
+ if ! command -v tdnf >/dev/null 2>&1; then
+ echo "rpmbuild is unavailable and tdnf is not installed; run this job in the configured Azure Linux container." >&2
+ exit 1
+ fi
+ tdnf install -y rpm-build
+ fi
+
+ rpm_root="$(Build.SourcesDirectory)/.rpmbuild"
+ output_dir="$(Build.SourcesDirectory)/out/rpm"
+ mkdir -p "$rpm_root"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} "$output_dir"
+ cp "$(Build.SourcesDirectory)/packaging/rpm/cosmosdbshell.spec" "$rpm_root/SPECS/"
+ cp "$(Build.SourcesDirectory)/LICENSE.md" "$(Build.SourcesDirectory)/NOTICE.html" "$rpm_root/SOURCES/"
+
+ for entry in "linux-x64:x86_64" "linux-arm64:aarch64"; do
+ rid="${entry%%:*}"
+ rpm_arch="${entry##*:}"
+ publish_dir="$(Build.SourcesDirectory)/.rpm-publish/$rid"
+
+ rm -rf "$publish_dir"
+ dotnet publish "$(Build.SourcesDirectory)/CosmosDBShell/CosmosDBShell.csproj" \
+ --configuration "$(BuildConfiguration)" \
+ --runtime "$rid" \
+ --self-contained false \
+ -p:PublishSingleFile=false \
+ -p:PackAsTool=false \
+ -p:CosmosDBShellExcludeMsalRuntime=true \
+ --output "$publish_dir" \
+ --configfile "$(Build.SourcesDirectory)/.pipelines/nuget.config"
+
+ # Ships for linux-x64 only and doubles the package size. The VS Code
+ # broker credential falls back when it is absent, as it already does
+ # on linux-arm64, where this library does not exist at all.
+ rm -f "$publish_dir/libmsalruntime.so"
+
+ for required in CosmosDBShell CosmosDBShell.dll; do
+ if [[ ! -f "$publish_dir/$required" ]]; then
+ echo "Expected $required in $publish_dir." >&2
+ exit 1
+ fi
+ done
+
+ tar -czf "$rpm_root/SOURCES/cosmosdbshell-payload.tar.gz" -C "$publish_dir" .
+ rpmbuild -bb "$rpm_root/SPECS/cosmosdbshell.spec" \
+ --target "$rpm_arch" \
+ --define "_topdir $rpm_root" \
+ --define "package_version $(CosmosDBShell_RpmVersion)" \
+ --define "package_release $(CosmosDBShell_RpmRelease)"
+ done
+
+ find "$rpm_root/RPMS" -type f -name '*.rpm' -exec cp {} "$output_dir/" \;
+ displayName: Build framework-dependent RPMs
+
+ - script: |
+ set -euo pipefail
+ output_dir="$(Build.SourcesDirectory)/out/rpm"
+ mapfile -t packages < <(find "$output_dir" -maxdepth 1 -type f -name '*.rpm' | sort)
+ if [[ ${#packages[@]} -ne 2 ]]; then
+ echo "Expected two RPM packages; found ${#packages[@]}." >&2
+ exit 1
+ fi
+
+ for package in "${packages[@]}"; do
+ size="$(stat -c %s "$package")"
+ if (( size > 25000000 )); then
+ echo "$(basename "$package") is $size bytes and exceeds the 25 MB Cloud Shell limit." >&2
+ exit 1
+ fi
+ rpm -qpR "$package" | grep -Fx 'dotnet-runtime-10.0 >= 10.0'
+ rpm -qlp "$package" | grep -Fx '/usr/bin/cosmosdbshell'
+ rpm -qlp "$package" | grep -Fx '/usr/libexec/cosmosdbshell/CosmosDBShell'
+ rpm -qlp "$package" | grep -Fx '/usr/libexec/cosmosdbshell/CosmosDBShell.dll'
+ if rpm -qlp "$package" | grep -q 'libmsalruntime\.so'; then
+ echo "$(basename "$package") still carries libmsalruntime.so." >&2
+ exit 1
+ fi
+ echo "Validated $(basename "$package") [$size bytes]"
+ done
+ displayName: Validate RPM packages
+
- job: CodeQLAnalyze
displayName: CodeQL (C#)
pool:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7c95e294..ba02fc3c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+### Build & pipeline
+
+- The official pipeline now produces compressed `x86_64` and `aarch64` RPM packages from the framework-dependent Linux builds. The packages require the .NET 10 runtime and are validated against the Azure Cloud Shell 25 MB package-size limit.
+
## 1.1.209-preview — 2026-08-26
### New features
diff --git a/CosmosDBShell.Tests/CommandTests/ConnectCommandTests.cs b/CosmosDBShell.Tests/CommandTests/ConnectCommandTests.cs
index 137147aa..27fefbd8 100644
--- a/CosmosDBShell.Tests/CommandTests/ConnectCommandTests.cs
+++ b/CosmosDBShell.Tests/CommandTests/ConnectCommandTests.cs
@@ -15,6 +15,16 @@ namespace CosmosShell.Tests.CommandTests;
[Collection(CosmosShell.Tests.Shell.ThemeStateTestCollection.Name)]
public class ConnectCommandTests
{
+ [Fact]
+ public void VSCodeCredential_SupportMatchesBuildCapability()
+ {
+#if COSMOSDBSHELL_NO_MSAL_RUNTIME
+ Assert.False(ShellInterpreter.IsVSCodeCredentialSupported);
+#else
+ Assert.True(ShellInterpreter.IsVSCodeCredentialSupported);
+#endif
+ }
+
[Fact]
public async Task ConnectAsync_CanceledToken_CancelsConnectionAttempt()
{
diff --git a/CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs b/CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs
index 4826518f..3640001a 100644
--- a/CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs
+++ b/CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs
@@ -110,6 +110,18 @@ internal ShellInterpreter(string? configPath = null)
///
public bool Echo { get; set; } = true;
+ internal static bool IsVSCodeCredentialSupported
+ {
+ get
+ {
+#if COSMOSDBSHELL_NO_MSAL_RUNTIME
+ return false;
+#else
+ return true;
+#endif
+ }
+ }
+
internal static CancellationTokenSource TokenSource
{
get
@@ -970,27 +982,34 @@ internal async Task ConnectAsync(string connectionString, string? loginHint = nu
// Step 2: VisualStudioCodeCredential (when launched from VS Code extension)
if (client == null && credentialMethod == CredentialMethod.VSCode)
{
- WriteLine(MessageService.GetString("shell-connect-vscode-credential-auth"));
-
- var vscOptions = new VisualStudioCodeCredentialOptions();
- if (!string.IsNullOrWhiteSpace(tenantId))
+ if (!IsVSCodeCredentialSupported)
{
- vscOptions.TenantId = tenantId;
+ WriteLine(MessageService.GetString("shell-connect-vscode-credential-msal-runtime-missing"));
}
-
- if (authorityHostUri != null)
+ else
{
- vscOptions.AuthorityHost = authorityHostUri;
- }
+ WriteLine(MessageService.GetString("shell-connect-vscode-credential-auth"));
- var vscCredential = new VisualStudioCodeCredential(vscOptions);
- if (await this.TryConnectWithTokenCredentialAsync(tokenEndpoint, vscCredential, options, subscriptionId, resourceGroupName, authorityHostUri, allowCredentialFallback: true, token))
- {
- return;
- }
+ var vscOptions = new VisualStudioCodeCredentialOptions();
+ if (!string.IsNullOrWhiteSpace(tenantId))
+ {
+ vscOptions.TenantId = tenantId;
+ }
- // VS Code credential unavailable or expired; continue the credential chain.
- WriteLine(MessageService.GetString("shell-connect-vscode-credential-fallback"));
+ if (authorityHostUri != null)
+ {
+ vscOptions.AuthorityHost = authorityHostUri;
+ }
+
+ var vscCredential = new VisualStudioCodeCredential(vscOptions);
+ if (await this.TryConnectWithTokenCredentialAsync(tokenEndpoint, vscCredential, options, subscriptionId, resourceGroupName, authorityHostUri, allowCredentialFallback: true, token))
+ {
+ return;
+ }
+
+ // VS Code credential unavailable or expired; continue the credential chain.
+ WriteLine(MessageService.GetString("shell-connect-vscode-credential-fallback"));
+ }
}
// Step 3: Static token from COSMOSDB_SHELL_TOKEN environment variable
diff --git a/CosmosDBShell/lang/en.ftl b/CosmosDBShell/lang/en.ftl
index d0a5ae95..02ebb4cf 100644
--- a/CosmosDBShell/lang/en.ftl
+++ b/CosmosDBShell/lang/en.ftl
@@ -17,6 +17,7 @@ shell-connect-static-token-auth = Connecting with externally provided access tok
shell-connect-static-token-expiry = Expires in { $timespan } (expiration: { $expiration }).
shell-connect-vscode-credential-auth = Connecting with Visual Studio Code credential...
shell-connect-vscode-credential-fallback = Visual Studio Code credential unavailable, falling back...
+shell-connect-vscode-credential-msal-runtime-missing = WARNING: Visual Studio Code credential is unavailable because this build excludes the native MSAL runtime. Falling back to other credentials; use --azure-cli to select the Azure CLI identity explicitly.
shell-connect-devicecode-fallback = Browser authentication failed, falling back to device code authentication...
shell-connect-arm-discovery-failed = Using Cosmos DB data plane.
shell-connect-arm-discovery-ambiguous = Multiple ARM Cosmos DB accounts match the connected endpoint. Reconnect with --subscription and --resource-group, or use --connect-subscription and --connect-resource-group at startup, to specify which account to use. Using Cosmos DB data plane for now.
diff --git a/Directory.Build.props b/Directory.Build.props
index 8dcb9c5e..c6db7613 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -15,4 +15,7 @@
+
+ $(DefineConstants);COSMOSDBSHELL_NO_MSAL_RUNTIME
+
diff --git a/README.md b/README.md
index 21011f6d..5efd2b7a 100644
--- a/README.md
+++ b/README.md
@@ -64,6 +64,35 @@ Run the tests:
dotnet test CosmosDBShell.sln
```
+## Install from RPM Artifacts
+
+The official pipeline produces framework-dependent RPMs for Azure Linux and
+other RPM-based distributions:
+
+- `cosmosdbshell--.x86_64.rpm`
+- `cosmosdbshell--.aarch64.rpm`
+
+The RPM requires the .NET 10 runtime package (`dotnet-runtime-10.0`), but does
+not require the .NET SDK. On Azure Linux, install the package that matches the
+host architecture with `tdnf`:
+
+```bash
+sudo tdnf install ./cosmosdbshell--..rpm
+cosmosdbshell
+```
+
+On other RPM-based distributions, use `dnf`:
+
+```bash
+sudo dnf install ./cosmosdbshell--..rpm
+cosmosdbshell
+```
+
+The RPM excludes the native MSAL runtime to remain within the Azure Cloud Shell
+package-size limit. Visual Studio Code credential authentication is unavailable
+in this build; selecting it prints a warning and falls back to other credentials.
+Use `--azure-cli` to select the signed-in Azure CLI identity explicitly.
+
## Architecture
| Folder | Purpose |
diff --git a/packaging/rpm/cosmosdbshell.spec b/packaging/rpm/cosmosdbshell.spec
new file mode 100644
index 00000000..6f967763
--- /dev/null
+++ b/packaging/rpm/cosmosdbshell.spec
@@ -0,0 +1,47 @@
+Name: cosmosdbshell
+Version: %{package_version}
+Release: %{package_release}%{?dist}
+Summary: Interactive shell for Azure Cosmos DB
+License: MIT
+URL: https://github.com/Azure/CosmosDBShell
+Source0: cosmosdbshell-payload.tar.gz
+Source1: LICENSE.md
+Source2: NOTICE.html
+Requires: dotnet-runtime-10.0 >= 10.0
+# The payload is prebuilt and only needs the .NET runtime, so skip the ELF scan
+# that would otherwise derive dependencies from the build host.
+AutoReqProv: no
+
+%global _binary_payload w19.zstdio
+%{!?_licensedir: %global _licensedir %{_datadir}/licenses}
+
+# Prebuilt binaries are shipped as published; stripping them breaks the .NET host.
+%global debug_package %{nil}
+%global __os_install_post %{nil}
+
+%description
+Azure Cosmos DB Shell is a command-line tool for interactive navigation,
+queries, scripting, and MCP server workflows with Azure Cosmos DB.
+
+%prep
+
+%build
+
+%install
+mkdir -p %{buildroot}%{_libexecdir}/cosmosdbshell
+tar -xzf %{SOURCE0} -C %{buildroot}%{_libexecdir}/cosmosdbshell
+chmod 0755 %{buildroot}%{_libexecdir}/cosmosdbshell/CosmosDBShell
+install -D -m 0644 %{SOURCE1} %{buildroot}%{_licensedir}/%{name}/LICENSE.md
+install -D -m 0644 %{SOURCE2} %{buildroot}%{_licensedir}/%{name}/NOTICE.html
+mkdir -p %{buildroot}%{_bindir}
+ln -s %{_libexecdir}/cosmosdbshell/CosmosDBShell %{buildroot}%{_bindir}/cosmosdbshell
+
+%files
+%{_bindir}/cosmosdbshell
+%{_libexecdir}/cosmosdbshell
+%license %{_licensedir}/%{name}/LICENSE.md
+%license %{_licensedir}/%{name}/NOTICE.html
+
+%changelog
+* Thu Aug 27 2026 Microsoft Corporation - %{package_version}-%{package_release}
+- Build from the framework-dependent .NET 10 publish output.
\ No newline at end of file