Skip to content
Open
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
21 changes: 20 additions & 1 deletion INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ irm https://raw.githubusercontent.com/CommandCodeAI/desktop/main/install.ps1 | i

1. Detect the operating system and supported CPU architecture.
2. Read the newest release from `CommandCodeAI/desktop`.
3. Select the matching `.dmg`, `.deb`, or `.exe`.
3. Select the matching `.dmg`, `.deb`, or `.exe` (or a native `.rpm` when
one is published).
4. Verify the SHA-256 digest published by GitHub.
5. Install and open Command Code.

Expand All @@ -37,6 +38,24 @@ The source is public:
- [install.sh](install.sh)
- [install.ps1](install.ps1)

## RPM-based Linux (Fedora, RHEL, openSUSE)

Releases currently publish a Linux `.deb`. On rpm-based distributions the
installer:

1. Downloads the `.deb` and verifies its SHA-256 digest first, exactly as on
Debian and Ubuntu.
2. Prefers a native `.rpm` automatically as soon as a release publishes one.
3. Unpacks the verified `.deb` and repacks the payload into a local RPM with
`rpmbuild`, then installs it with `dnf` or `zypper` so the package is
tracked and removable through the package manager.
4. Falls back to installing the payload directly (with the same file layout,
launcher, and desktop entry) when `rpmbuild` is not available; that
variant prints the exact paths to remove when uninstalling.

Uninstall a local-RPM installation with `sudo dnf remove command-code` (or
`sudo zypper remove command-code`).

## Install a specific version

macOS or Linux:
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ Running the same command again installs the newest available version.
### macOS and Linux

One command for both. The script detects the system - Apple silicon or Intel
macOS, or Ubuntu/Debian x64 Linux - and installs the matching build:
macOS, or x64 Linux (Debian, Ubuntu, Fedora, RHEL, openSUSE) - and installs
the matching build:

```bash
curl -fsSL https://raw.githubusercontent.com/CommandCodeAI/desktop/main/install.sh | bash
Expand Down Expand Up @@ -72,6 +73,10 @@ automatically. Manual packages are available on the
| Linux | `.deb` | x64 |
| Windows | `.exe` | x64 |

On Fedora, RHEL, and openSUSE, the installer prefers a native `.rpm` when a
release publishes one and otherwise installs from the verified `.deb`,
repacking it into a local RPM on the machine.

## What you can do

- Organize projects and session history
Expand Down
276 changes: 264 additions & 12 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ REPOSITORY="CommandCodeAI/desktop"
API_ROOT="https://api.github.com/repos/$REPOSITORY"
TEMPORARY_DIRECTORY="$(mktemp -d "${TMPDIR:-/tmp}/command-code-install.XXXXXX")"
MOUNT_POINT=""
linux_flavor=""
linux_installer=""

cleanup() {
if [ -n "$MOUNT_POINT" ]; then
Expand Down Expand Up @@ -98,6 +100,37 @@ verify_sha256() {
fail "Downloaded file failed SHA-256 verification."
}

detect_linux_package_manager() {
if command -v apt-get >/dev/null 2>&1; then
linux_flavor="apt"
linux_installer="apt-get"
elif command -v dnf >/dev/null 2>&1; then
linux_flavor="rpm"
linux_installer="dnf"
elif command -v zypper >/dev/null 2>&1; then
linux_flavor="rpm"
linux_installer="zypper"
elif command -v yum >/dev/null 2>&1; then
linux_flavor="rpm"
linux_installer="yum"
fi
}

# On rpm-based systems prefer a native .rpm when the release publishes one;
# otherwise fall back to the .deb, which this installer repacks locally.
artifact_candidates() {
version_for_artifacts="$1"

if [ "${linux_flavor:-}" = "rpm" ]; then
printf '%s\n' \
"CommandCode-$version_for_artifacts-x86_64.rpm" \
"CommandCode-$version_for_artifacts-$artifact_architecture.$artifact_extension"
else
printf '%s\n' \
"CommandCode-$version_for_artifacts-$artifact_architecture.$artifact_extension"
fi
}

replace_macos_app() {
staged_app="$1"
destination="/Applications/Command Code.app"
Expand Down Expand Up @@ -187,13 +220,228 @@ install_macos() {
open "$destination"
}

extract_deb_payload() {
deb_path="$1"
payload_root="$2"
payload_archive=""

if command -v ar >/dev/null 2>&1; then
extract_directory="$TEMPORARY_DIRECTORY/deb"
mkdir -p "$extract_directory"
(cd "$extract_directory" && ar x "$deb_path") ||
fail "Could not unpack the downloaded .deb package."
for archive_member in data.tar.xz data.tar.gz; do
if [ -f "$extract_directory/$archive_member" ]; then
payload_archive="$extract_directory/$archive_member"
break
fi
done
elif command -v bsdtar >/dev/null 2>&1; then
for archive_member in data.tar.xz data.tar.gz; do
if bsdtar -x -f "$deb_path" -C "$TEMPORARY_DIRECTORY" "$archive_member" >/dev/null 2>&1; then
payload_archive="$TEMPORARY_DIRECTORY/$archive_member"
break
fi
done
else
fail "ar (binutils) or bsdtar is required to unpack the package on this system."
fi

[ -n "$payload_archive" ] ||
fail "Could not find the payload inside the downloaded .deb package."
mkdir -p "$payload_root"
tar -xf "$payload_archive" -C "$payload_root" ||
fail "Could not extract the payload from the downloaded .deb package."

[ -x "$payload_root/opt/Command Code/command-code" ] ||
fail "The package payload is missing the Command Code executable."
[ -s "$payload_root/usr/share/applications/command-code.desktop" ] ||
fail "The package payload is missing the desktop entry."
[ -s "$payload_root/opt/Command Code/resources/app/node_modules/command-code/dist/cli.mjs" ] ||
fail "The package payload is missing the compiled engine."
}

install_rpm_package() {
rpm_path="$1"

printf 'Administrator permission is required to install the package.\n'
case "$linux_installer" in
dnf | yum)
if ! sudo "$linux_installer" install -y "$rpm_path" >/dev/null; then
sudo "$linux_installer" reinstall -y "$rpm_path" ||
fail "Could not install the RPM package."
fi
;;
zypper)
sudo zypper --non-interactive install "$rpm_path" ||
fail "Could not install the RPM package."
;;
*)
fail "Unsupported package manager: $linux_installer."
;;
esac
}

install_linux_rpm_from_deb() {
deb_path="$1"
payload_root="$TEMPORARY_DIRECTORY/payload"

printf 'This release has no native .rpm; repacking the verified .deb.\n'
extract_deb_payload "$deb_path" "$payload_root"

if command -v rpmbuild >/dev/null 2>&1; then
install_linux_rpm_via_rpmbuild "$payload_root"
else
install_linux_rpm_direct "$payload_root"
fi
}

install_linux_rpm_via_rpmbuild() {
payload_root="$1"
rpm_version="${version//-/.}"
build_root="$TEMPORARY_DIRECTORY/rpmbuild"
spec_path="$build_root/SPECS/command-code.spec"
doc_install=""
doc_files=""

mkdir -p "$build_root/BUILD" "$build_root/BUILDROOT" "$build_root/RPMS" \
"$build_root/SOURCES" "$build_root/SPECS" "$build_root/SRPMS"
if [ -d "$payload_root/usr/share/doc/command-code" ]; then
doc_install="mkdir -p %{buildroot}%{_docdir}/%{name}
cp -a '$payload_root/usr/share/doc/command-code/.' %{buildroot}%{_docdir}/%{name}/"
doc_files="%{_docdir}/%{name}/*"
fi

cat >"$spec_path" <<SPEC
Name: command-code
Version: $rpm_version
Release: 1%{?dist}
Summary: Command Code desktop app
License: Apache-2.0
URL: https://github.com/CommandCodeAI/desktop

# The payload bundles optional native modules for other platforms (arm64,
# musl, Windows, macOS); the automatic dependency scan would emit requires
# that do not exist on this system. Runtime dependencies are explicit below.
AutoReq: no
%define debug_package %{nil}
%define __os_install_post %{nil}

Requires: gtk3
Requires: libnotify
Requires: nss
Requires: nspr
Requires: libXScrnSaver
Requires: libXtst
Requires: xdg-utils
Requires: at-spi2-core
Requires: libuuid
Requires: libsecret
Requires: alsa-lib

%description
Command Code desktop app, installed from the official verified release.

%prep

%build

%install
rm -rf %{buildroot}
mkdir -p %{buildroot}/opt
cp -a '$payload_root/opt/.' %{buildroot}/opt/
mkdir -p %{buildroot}%{_bindir}
ln -s '/opt/Command Code/command-code' %{buildroot}%{_bindir}/command-code
mkdir -p %{buildroot}%{_datadir}
cp -a '$payload_root/usr/share/applications' %{buildroot}%{_datadir}/
cp -a '$payload_root/usr/share/icons' %{buildroot}%{_datadir}/
$doc_install

%files
/opt/Command*
%{_bindir}/command-code
%{_datadir}/applications/command-code.desktop
%{_datadir}/icons/hicolor/*/apps/command-code.png
$doc_files

%post
chmod 4755 '/opt/Command Code/chrome-sandbox' >/dev/null 2>&1 || :
if [ -x /usr/bin/update-desktop-database ]; then
/usr/bin/update-desktop-database /usr/share/applications >/dev/null 2>&1 || :
fi
if [ -x /usr/bin/gtk-update-icon-cache ]; then
/usr/bin/gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor >/dev/null 2>&1 || :
fi
if [ -x /usr/sbin/restorecon ]; then
/usr/sbin/restorecon -R '/opt/Command Code' /usr/bin/command-code >/dev/null 2>&1 || :
fi

%postun
if [ -x /usr/bin/gtk-update-icon-cache ]; then
/usr/bin/gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor >/dev/null 2>&1 || :
fi
SPEC

if ! rpmbuild -bb --define "_topdir $build_root" "$spec_path" \
>"$TEMPORARY_DIRECTORY/rpmbuild.log" 2>&1; then
build_error="$(tr '\n' ' ' <"$TEMPORARY_DIRECTORY/rpmbuild.log" | cut -c1-300)"
fail "Could not build a local RPM from the verified download. $build_error"
fi

built_rpm="$(find "$build_root/RPMS" -type f -name '*.rpm' -print -quit)"
[ -n "$built_rpm" ] ||
fail "The local RPM build produced no package."

install_rpm_package "$built_rpm"
}

install_linux_rpm_direct() {
payload_root="$1"

printf 'rpmbuild is not installed; installing the payload directly.\n'
printf 'Administrator permission is required to install the package.\n'
sudo cp -a "$payload_root/opt/." /opt/ ||
fail "Could not copy Command Code into /opt."
sudo mkdir -p /usr/share/applications /usr/share/icons
sudo cp -a "$payload_root/usr/share/applications/command-code.desktop" /usr/share/applications/ ||
fail "Could not install the desktop entry."
sudo cp -a "$payload_root/usr/share/icons/." /usr/share/icons/ ||
fail "Could not install the icons."
if [ -d "$payload_root/usr/share/doc/command-code" ]; then
sudo mkdir -p /usr/share/doc/command-code
sudo cp -a "$payload_root/usr/share/doc/command-code/." /usr/share/doc/command-code/ || true
fi
sudo ln -sf '/opt/Command Code/command-code' /usr/bin/command-code ||
fail "Could not create the command-code launcher."
sudo chmod 4755 '/opt/Command Code/chrome-sandbox'
sudo update-desktop-database /usr/share/applications >/dev/null 2>&1 || true
sudo gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor >/dev/null 2>&1 || true
sudo restorecon -R '/opt/Command Code' /usr/bin/command-code >/dev/null 2>&1 || true

printf 'To remove this direct installation, run:\n'
printf " sudo rm -rf '/opt/Command Code' /usr/bin/command-code /usr/share/doc/command-code\n"
printf ' sudo rm -f /usr/share/applications/command-code.desktop\n'
printf ' sudo rm -f /usr/share/icons/hicolor/*/apps/command-code.png\n'
}

install_linux() {
archive_path="$1"

command -v apt-get >/dev/null 2>&1 ||
fail "This preview currently supports Debian and Ubuntu through a .deb package."
printf 'Administrator permission is required to install the package.\n'
sudo apt-get install -y "$archive_path"
case "$linux_flavor" in
apt)
printf 'Administrator permission is required to install the package.\n'
sudo apt-get install -y "$archive_path"
;;
rpm)
case "$artifact_extension" in
rpm) install_rpm_package "$archive_path" ;;
deb) install_linux_rpm_from_deb "$archive_path" ;;
esac
;;
*)
fail "This preview supports Debian and Ubuntu through a .deb package and Fedora, RHEL, and openSUSE through the installer."
;;
esac
}

require_command curl
Expand Down Expand Up @@ -229,6 +477,9 @@ case "$operating_system:$machine_architecture" in
platform="Linux"
artifact_architecture="amd64"
artifact_extension="deb"
detect_linux_package_manager
[ -n "$linux_flavor" ] ||
fail "No supported package manager found. This preview supports Debian and Ubuntu through a .deb package and Fedora, RHEL, and openSUSE through the installer."
;;
Darwin:*)
fail "The current macOS preview supports Apple silicon only."
Expand Down Expand Up @@ -274,14 +525,15 @@ asset_metadata=""
while IFS= read -r candidate_version; do
[ -n "$candidate_version" ] || continue
is_valid_version "$candidate_version" || continue
candidate_artifact="CommandCode-$candidate_version-$artifact_architecture.$artifact_extension"
candidate_metadata="$(read_asset_metadata "$candidate_artifact" "$release_file")"
if [ -n "$candidate_metadata" ]; then
version="$candidate_version"
artifact_name="$candidate_artifact"
asset_metadata="$candidate_metadata"
break
fi
for candidate_artifact in $(artifact_candidates "$candidate_version"); do
candidate_metadata="$(read_asset_metadata "$candidate_artifact" "$release_file")"
if [ -n "$candidate_metadata" ]; then
version="$candidate_version"
artifact_name="$candidate_artifact"
asset_metadata="$candidate_metadata"
break 2
fi
done
done <<EOF
$release_versions
EOF
Expand Down