Add install.sh with an app-menu launcher
install.sh builds the image (run.sh --build-only) and adds a "Claude RE Workstation" desktop entry that starts this checkout's run.sh, with Claude Desktop's icons taken from the image under their own name. --uninstall removes the launcher and icons again. run.sh gains --build-only, which needs no display, and shows errors in a zenity dialog when it has no terminal, as when started from the menu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ The goal is that `docker compose up` gives you a working setup where Claude Desk
|
||||
|
||||
## Status
|
||||
|
||||
Early stage. What exists: a single image (`Dockerfile`, `launch-apps.sh`) with Claude Desktop, Ghidra (plus GhidraMCP and an ESP32 loader), binwalk, unblob, esptool and REA, started by `run.sh` as two containers (Claude Desktop + Ghidra, and the REA MCP server); `README.md` covers usage; Claude Desktop's MCP config is `claude-desktop/claude_desktop_config.json`; Ghidra config files are in `ghidra/`. No compose setup yet. The layout below is the **intended** structure. Update this file when the real structure differs.
|
||||
Early stage. What exists: a single image (`Dockerfile`, `launch-apps.sh`) with Claude Desktop, Ghidra (plus GhidraMCP and an ESP32 loader), binwalk, unblob, esptool and REA, started by `run.sh` (or the app-menu launcher `install.sh` creates: `~/.local/share/applications/claude-re-setup.desktop`, icons taken from the image, `Exec` pointing at this checkout's `run.sh`; `run.sh` shows errors in a zenity dialog when it has no terminal) as two containers (Claude Desktop + Ghidra, and the REA MCP server); `README.md` covers usage; Claude Desktop's MCP config is `claude-desktop/claude_desktop_config.json`; Ghidra config files are in `ghidra/`. No compose setup yet. The layout below is the **intended** structure. Update this file when the real structure differs.
|
||||
|
||||
## Intended layout
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ Docker and an X server (or XWayland) is needed on the host.
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
./install.sh # build the image and add "Claude RE Workstation" to the app menu
|
||||
./install.sh --uninstall # remove that launcher again
|
||||
./run.sh # build the image if needed, pick a folder to share, start
|
||||
./run.sh --build # rebuild the image first
|
||||
./run.sh --workspace ~/re/target-x # share this folder without asking
|
||||
@@ -18,6 +20,9 @@ The shared folder is mounted at `/workspace` in both containers. Sign in to
|
||||
Claude Desktop on every start; the sign-in is not kept. Closing Claude
|
||||
Desktop or Ghidra's project window stops everything.
|
||||
|
||||
The launcher runs `run.sh` from this checkout, so re-run `./install.sh` after
|
||||
moving it. Started from the menu, errors appear in a dialog.
|
||||
|
||||
## What runs where
|
||||
|
||||
| Container | Contents | Network |
|
||||
|
||||
Executable
+99
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build the image and add a "Claude RE Workstation" launcher to the desktop's
|
||||
# application menu. The launcher runs ./run.sh from this checkout, so keep
|
||||
# the checkout where it is (re-run this script after moving it).
|
||||
#
|
||||
# Usage: ./install.sh [--uninstall]
|
||||
# --uninstall remove the launcher and its icons (the image is kept;
|
||||
# remove it with: docker rmi claude-re-setup:latest)
|
||||
#
|
||||
# RUNTIME=podman is passed on to the launcher.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
IMAGE="${IMAGE:-claude-re-setup:latest}"
|
||||
RUNTIME="${RUNTIME:-docker}"
|
||||
|
||||
app_id="claude-re-setup"
|
||||
script_dir="$(dirname "$(readlink -f "$0")")"
|
||||
data_home="${XDG_DATA_HOME:-$HOME/.local/share}"
|
||||
desktop_file="$data_home/applications/$app_id.desktop"
|
||||
icon_root="$data_home/icons/hicolor"
|
||||
icon_sizes=(16 32 48 128 256)
|
||||
|
||||
die() { echo "error: $*" >&2; exit 1; }
|
||||
info() { echo "==> $*"; }
|
||||
|
||||
refresh_caches() {
|
||||
command -v update-desktop-database >/dev/null \
|
||||
&& update-desktop-database -q "$data_home/applications" || true
|
||||
command -v gtk-update-icon-cache >/dev/null && [[ -f "$icon_root/index.theme" ]] \
|
||||
&& gtk-update-icon-cache -q -t "$icon_root" || true
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
--uninstall)
|
||||
rm -f "$desktop_file"
|
||||
for size in "${icon_sizes[@]}"; do
|
||||
rm -f "$icon_root/${size}x${size}/apps/$app_id.png"
|
||||
done
|
||||
refresh_caches
|
||||
info "Removed the launcher. The image is still there: $RUNTIME rmi $IMAGE"
|
||||
exit 0 ;;
|
||||
-h|--help) sed -n '2,10p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
"") ;;
|
||||
*) die "unknown argument: $1" ;;
|
||||
esac
|
||||
|
||||
# The desktop entry quotes the path; these characters would need escaping
|
||||
# there, so such a checkout location is refused instead.
|
||||
[[ "$script_dir" != *[\"\`\$\\%]* ]] \
|
||||
|| die "the checkout path contains a character a .desktop file can't hold: $script_dir"
|
||||
|
||||
# --- Image -------------------------------------------------------------------
|
||||
info "Building $IMAGE (cached layers are reused)"
|
||||
RUNTIME="$RUNTIME" IMAGE="$IMAGE" "$script_dir/run.sh" --build-only
|
||||
|
||||
# --- Icons -------------------------------------------------------------------
|
||||
# Claude Desktop's own icons, taken from the image, under this launcher's
|
||||
# name so they don't clash with a natively installed Claude Desktop.
|
||||
info "Installing icons"
|
||||
cid="$("$RUNTIME" create "$IMAGE")"
|
||||
trap '"$RUNTIME" rm "$cid" >/dev/null 2>&1 || true' EXIT
|
||||
for size in "${icon_sizes[@]}"; do
|
||||
dir="$icon_root/${size}x${size}/apps"
|
||||
mkdir -p "$dir"
|
||||
"$RUNTIME" cp "$cid:/usr/share/icons/hicolor/${size}x${size}/apps/claude-desktop.png" \
|
||||
"$dir/$app_id.png"
|
||||
done
|
||||
|
||||
# --- Desktop entry -----------------------------------------------------------
|
||||
exec_line="\"$script_dir/run.sh\""
|
||||
[[ "$RUNTIME" != docker ]] && exec_line="env RUNTIME=$RUNTIME $exec_line"
|
||||
|
||||
info "Writing $desktop_file"
|
||||
mkdir -p "$(dirname "$desktop_file")"
|
||||
cat > "$desktop_file" <<EOF
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Claude RE Workstation
|
||||
GenericName=Reverse-engineering workstation
|
||||
Comment=Claude Desktop with Ghidra and REA in Docker
|
||||
Keywords=Claude;Ghidra;REA;Reverse;Engineering;Firmware;
|
||||
Exec=$exec_line
|
||||
Path=$script_dir
|
||||
Icon=$app_id
|
||||
Terminal=false
|
||||
Categories=Development;
|
||||
StartupNotify=true
|
||||
# Claude Desktop's windows in the container carry this class, so docks
|
||||
# group them under this launcher.
|
||||
StartupWMClass=com.anthropic.Claude
|
||||
EOF
|
||||
|
||||
if command -v desktop-file-validate >/dev/null; then
|
||||
desktop-file-validate "$desktop_file"
|
||||
fi
|
||||
refresh_caches
|
||||
|
||||
info "Done. Start \"Claude RE Workstation\" from the application menu."
|
||||
@@ -6,8 +6,9 @@
|
||||
# Also starts the REA MCP server in a second, isolated container and stops it
|
||||
# when Claude Desktop's container ends.
|
||||
#
|
||||
# Usage: ./run.sh [--build] [--check-only] [--workspace DIR]
|
||||
# Usage: ./run.sh [--build] [--build-only] [--check-only] [--workspace DIR]
|
||||
# --build rebuild the image even if it exists
|
||||
# --build-only (re)build the image and exit
|
||||
# --check-only run the display check and exit without starting Claude
|
||||
# --workspace DIR share DIR without asking (also: WORKSPACE=DIR)
|
||||
|
||||
@@ -27,23 +28,50 @@ last_workspace_file="$state_dir/last_workspace"
|
||||
|
||||
build=0
|
||||
check_only=0
|
||||
build_only=0
|
||||
while (( $# )); do
|
||||
case "$1" in
|
||||
--build) build=1 ;;
|
||||
--build-only) build=1 build_only=1 ;;
|
||||
--check-only) check_only=1 ;;
|
||||
--workspace) [[ $# -ge 2 ]] || { echo "--workspace needs a directory" >&2; exit 2; }
|
||||
WORKSPACE="$2"; shift ;;
|
||||
--workspace=*) WORKSPACE="${1#*=}" ;;
|
||||
-h|--help) sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
-h|--help) sed -n '2,13p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
*) echo "Unknown argument: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
die() { echo "error: $*" >&2; exit 1; }
|
||||
# Started from the app menu (install.sh) there is no terminal to read
|
||||
# errors in, so they are also shown in a dialog.
|
||||
die() {
|
||||
echo "error: $*" >&2
|
||||
if [[ ! -t 2 ]] && command -v zenity >/dev/null; then
|
||||
zenity --error --no-wrap --title="Claude RE workstation" --text="$*" 2>/dev/null || true
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
info() { echo "==> $*"; }
|
||||
|
||||
command -v "$RUNTIME" >/dev/null || die "'$RUNTIME' not found (set RUNTIME=podman to use podman)"
|
||||
|
||||
# --- Image build -------------------------------------------------------------
|
||||
ensure_image() {
|
||||
if (( build )) || ! "$RUNTIME" image inspect "$IMAGE" >/dev/null 2>&1; then
|
||||
info "Building $IMAGE"
|
||||
"$RUNTIME" build \
|
||||
--build-arg USER_UID="$(id -u)" \
|
||||
--build-arg USER_GID="$(id -g)" \
|
||||
-t "$IMAGE" "$script_dir"
|
||||
fi
|
||||
}
|
||||
if (( build_only )); then
|
||||
ensure_image
|
||||
info "Built $IMAGE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
command -v xauth >/dev/null || die "xauth not found on the host (dnf install xorg-x11-xauth)"
|
||||
|
||||
# --- Display -----------------------------------------------------------------
|
||||
@@ -69,13 +97,7 @@ xauth nlist "$DISPLAY" | sed -e 's/^..../ffff/' | xauth -f "$xauth_file" nmerge
|
||||
chmod 600 "$xauth_file"
|
||||
|
||||
# --- Image -------------------------------------------------------------------
|
||||
if (( build )) || ! "$RUNTIME" image inspect "$IMAGE" >/dev/null 2>&1; then
|
||||
info "Building $IMAGE"
|
||||
"$RUNTIME" build \
|
||||
--build-arg USER_UID="$(id -u)" \
|
||||
--build-arg USER_GID="$(id -g)" \
|
||||
-t "$IMAGE" "$script_dir"
|
||||
fi
|
||||
ensure_image
|
||||
|
||||
# label=disable: the X socket and cookie are host files that SELinux would
|
||||
# otherwise block, and relabeling /tmp/.X11-unix with :z would break the host.
|
||||
|
||||
Reference in New Issue
Block a user