Last active
July 5, 2026 14:04
-
-
Save amoilanen/1adf35d894ab73eecbacb6dc0870648f to your computer and use it in GitHub Desktop.
Install Claude Desktop and Zenflow on an Arch based Linux
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # | |
| # Install the latest Claude Desktop on any Arch Linux derivative | |
| # (Arch, Manjaro, EndeavourOS, CachyOS, Garuda, ...). | |
| # | |
| # Anthropic only ships Claude Desktop as a Debian .deb (amd64). Rather than | |
| # rely on dpkg, we resolve the newest package from Anthropic's apt repository, | |
| # verify its SHA256, extract it, and copy the /opt and /usr payload into place. | |
| set -euo pipefail | |
| REPO_BASE="https://downloads.claude.ai/claude-desktop/apt/stable" | |
| PACKAGES_URL="${REPO_BASE}/dists/stable/main/binary-amd64/Packages" | |
| # Work relative to this script's location, not the current working directory. | |
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| cd "$SCRIPT_DIR" | |
| DATE_SUFFIX="$(date +%d_%m_%Y)" | |
| DEB_FILE="ClaudeDesktop.deb" | |
| # --- helpers --------------------------------------------------------------- | |
| have() { command -v "$1" >/dev/null 2>&1; } | |
| die() { printf 'error: %s\n' "$*" >&2; exit 1; } | |
| # Run a command as root, using sudo (or doas) only when we are not already root. | |
| as_root() { | |
| if [ "$(id -u)" -eq 0 ]; then | |
| "$@" | |
| elif have sudo; then | |
| sudo "$@" | |
| elif have doas; then | |
| doas "$@" | |
| else | |
| die "need root privileges but neither sudo nor doas is available" | |
| fi | |
| } | |
| # Fetch a URL. With a second arg, save to that file; otherwise stream to stdout. | |
| fetch() { | |
| local url="$1" out="${2:-}" | |
| if have curl; then | |
| if [ -n "$out" ]; then curl -fL -o "$out" "$url"; else curl -fsSL "$url"; fi | |
| elif have wget; then | |
| if [ -n "$out" ]; then wget -O "$out" "$url"; else wget -qO- "$url"; fi | |
| else | |
| die "need a downloader: install 'curl' or 'wget'" | |
| fi | |
| } | |
| # Unpack a .deb (an 'ar' archive whose data.tar.* holds the payload) into $2. | |
| extract_deb() { | |
| local deb="$1" dest="$2" | |
| if have bsdtar; then | |
| # Layer 1: the .deb 'ar' container -> yields data.tar.* | |
| # Layer 2: the data tarball -> yields the opt/ and usr/ trees. | |
| # bsdtar (libarchive) handles the ar container and every compression | |
| # (gz/xz/zst) without needing binutils' 'ar' or a matching GNU tar. | |
| bsdtar -xf "$deb" -C "$dest" | |
| bsdtar -xf "$dest"/data.tar.* -C "$dest" | |
| elif have ar; then | |
| ( cd "$dest" && ar x "$deb" && tar -xf data.tar.* ) | |
| else | |
| die "no .deb extractor found: install 'libarchive' (bsdtar) or 'binutils' (ar)" | |
| fi | |
| } | |
| # --- preflight ------------------------------------------------------------- | |
| ARCH="$(uname -m)" | |
| case "$ARCH" in | |
| x86_64|amd64) ;; | |
| *) die "Claude Desktop is only published for x86_64 (amd64); this system is $ARCH" ;; | |
| esac | |
| have sha256sum || die "need 'sha256sum' (coreutils) to verify the download" | |
| # --- resolve latest version ------------------------------------------------ | |
| echo "==> Resolving latest Claude Desktop version..." | |
| read -r VERSION FILENAME SHA256 < <( | |
| fetch "$PACKAGES_URL" | awk ' | |
| /^Version:/ { v = $2 } | |
| /^Filename:/ { f = $2 } | |
| /^SHA256:/ { s = $2 } | |
| /^$/ { if (v != "") print v "\t" f "\t" s; v = f = s = "" } | |
| END { if (v != "") print v "\t" f "\t" s } | |
| ' | sort -V | tail -1 | |
| ) | |
| [ -n "${FILENAME:-}" ] || die "could not determine latest package from $PACKAGES_URL" | |
| echo " Latest version: $VERSION" | |
| # --- download -------------------------------------------------------------- | |
| DEB_URL="${REPO_BASE}/${FILENAME}" | |
| echo "==> Downloading $DEB_URL" | |
| fetch "$DEB_URL" "$DEB_FILE" | |
| cp "$DEB_FILE" "ClaudeDesktop_${VERSION}_${DATE_SUFFIX}.deb" | |
| echo "==> Verifying SHA256 checksum..." | |
| echo "${SHA256} ${DEB_FILE}" | sha256sum -c - || die "checksum verification failed" | |
| # --- extract --------------------------------------------------------------- | |
| # Unpack into a temporary staging directory so we never depend on / pollute | |
| # the working directory's layout. | |
| echo "==> Extracting package..." | |
| STAGE="$(mktemp -d "${TMPDIR:-/tmp}/claude-desktop.XXXXXX")" | |
| trap 'rm -rf "$STAGE"' EXIT | |
| extract_deb "$SCRIPT_DIR/$DEB_FILE" "$STAGE" | |
| # --- install --------------------------------------------------------------- | |
| # Copy whatever top-level system trees the package provides (typically opt/ and | |
| # usr/) into the filesystem root. | |
| echo "==> Installing to /opt and /usr (requires root)..." | |
| installed=0 | |
| for tree in opt usr; do | |
| if [ -d "$STAGE/$tree" ]; then | |
| as_root cp -a "$STAGE/$tree/." "/$tree/" | |
| installed=1 | |
| fi | |
| done | |
| [ "$installed" -eq 1 ] || die "extracted package contained no opt/ or usr/ tree" | |
| echo "==> Updating desktop/icon caches..." | |
| have update-desktop-database && as_root update-desktop-database /usr/share/applications 2>/dev/null || true | |
| have gtk-update-icon-cache && as_root gtk-update-icon-cache -f /usr/share/icons/hicolor 2>/dev/null || true | |
| echo "==> Done. Claude Desktop $VERSION installed. Launch with: claude-desktop" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # | |
| # Install Zenflow on any Arch Linux derivative (Arch, Manjaro, EndeavourOS, ...). | |
| # | |
| # The upstream package ships as an .rpm. We do not rely on any RPM-specific | |
| # tooling being present: extraction is done with bsdtar, which is provided by | |
| # libarchive, a mandatory dependency of pacman and therefore guaranteed to | |
| # exist on every Arch-based system. Fallbacks are provided just in case. | |
| set -euo pipefail | |
| URL="https://download.zencoder.ai/zenflowapp/latest/linux-x64/Zenflow.rpm" | |
| # Work relative to this script's location, not the current working directory. | |
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| cd "$SCRIPT_DIR" | |
| DATE_SUFFIX="$(date +%d_%m_%Y)" | |
| RPM="Zenflow.rpm" | |
| # --- helpers --------------------------------------------------------------- | |
| have() { command -v "$1" >/dev/null 2>&1; } | |
| die() { printf 'error: %s\n' "$*" >&2; exit 1; } | |
| # Run a command as root, using sudo (or doas) only when we are not already root. | |
| as_root() { | |
| if [ "$(id -u)" -eq 0 ]; then | |
| "$@" | |
| elif have sudo; then | |
| sudo "$@" | |
| elif have doas; then | |
| doas "$@" | |
| else | |
| die "need root privileges but neither sudo nor doas is available" | |
| fi | |
| } | |
| # --- download -------------------------------------------------------------- | |
| if have curl; then | |
| curl -fL -o "$RPM" "$URL" | |
| elif have wget; then | |
| wget -O "$RPM" "$URL" | |
| else | |
| die "need a downloader: install 'curl' or 'wget'" | |
| fi | |
| cp "$RPM" "Zenflow_${DATE_SUFFIX}.rpm" | |
| # --- extract --------------------------------------------------------------- | |
| # Unpack into a temporary staging directory so we never depend on / pollute | |
| # the working directory's layout. | |
| STAGE="$(mktemp -d "${TMPDIR:-/tmp}/zenflow.XXXXXX")" | |
| trap 'rm -rf "$STAGE"' EXIT | |
| if have bsdtar; then | |
| # bsdtar (libarchive) reads .rpm directly and is always present on Arch. | |
| bsdtar -xf "$SCRIPT_DIR/$RPM" -C "$STAGE" | |
| elif have rpm2cpio && have cpio; then | |
| ( cd "$STAGE" && rpm2cpio "$SCRIPT_DIR/$RPM" | cpio -idmv ) | |
| elif have rpmextract.sh; then | |
| ( cd "$STAGE" && rpmextract.sh "$SCRIPT_DIR/$RPM" ) | |
| else | |
| die "no RPM extractor found: install 'libarchive' (bsdtar) or 'rpmextract'" | |
| fi | |
| # --- install --------------------------------------------------------------- | |
| # Copy whatever top-level system trees the package provides (typically opt/ and | |
| # usr/) into the filesystem root. | |
| installed=0 | |
| for tree in opt usr; do | |
| if [ -d "$STAGE/$tree" ]; then | |
| as_root cp -a "$STAGE/$tree/." "/$tree/" | |
| installed=1 | |
| fi | |
| done | |
| [ "$installed" -eq 1 ] || die "extracted package contained no opt/ or usr/ tree" | |
| echo "Zenflow installed successfully." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment