Skip to content

Instantly share code, notes, and snippets.

@mygrexit
Last active April 17, 2026 11:00
Show Gist options
  • Select an option

  • Save mygrexit/480c64bbd65c488dd7521e7126fd2cfd to your computer and use it in GitHub Desktop.

Select an option

Save mygrexit/480c64bbd65c488dd7521e7126fd2cfd to your computer and use it in GitHub Desktop.
Claude Code on Unraid - Docker installer with interactive setup

Claude Code on Unraid

Run Claude Code (Anthropic's official CLI) in a Docker container on your Unraid server.


Disclaimer

This installer was vibe-coded and works on my setup. It has not been tested on every possible Unraid configuration. Use it at your own risk. You are fully responsible for what you install, what volumes you mount, and what permissions you give Claude. Review the generated docker-compose.yml before running it. No warranty, no guarantees, no support obligation.

Especially: Think carefully about which shares you expose and whether you grant Docker socket access. Claude with Docker socket access can manage your containers — including stopping, starting, and removing them.


Prerequisites

  • Unraid 6.12+ (older versions may work but are untested)
  • Docker enabled in Unraid (Settings > Docker > Enable Docker: Yes)
  • Compose Manager plugin installed from Community Applications
    • Open the CA tab, search "Compose Manager", install it
    • This provides docker compose which the installer needs
  • SSH access to your Unraid server
    • The Unraid web terminal does not work — it can't handle interactive input
    • Enable SSH: Settings > Management Access > Use SSH: Yes

Installation

SSH into your Unraid server:

ssh root@<your-unraid-ip>

Download and run the installer:

curl -fsSL https://gist.githubusercontent.com/mygrexit/480c64bbd65c488dd7521e7126fd2cfd/raw/a69cefee2a7203290ad7e2a36c7506c34a0bcc87/install.sh -o /tmp/install.sh
bash /tmp/install.sh

Do not pipe directly with curl | bash — this can cause issues with interactive prompts on some Unraid setups.

The installer will walk you through:

  1. Workspace directory — where Claude works (default: /mnt/<pool>/appdata/claude-workspace)
  2. Volume selection — interactive menu to mount your shares (arrow keys + space to toggle skip/ro/rw)
  3. Docker socket access — lets Claude manage your containers (default: yes)
  4. Build & start — builds the Docker image and starts the container

Usage

After installation, start Claude Code from any SSH session or the Unraid console:

claude

This opens a tmux session inside the container. Useful shortcuts:

Action Keys
Detach (leave running) Ctrl+B, then D
Reattach claude
Copy Select text normally, then Ctrl+Shift+C
Paste Ctrl+Shift+V
Scroll Mouse wheel
Toggle full mouse Ctrl+B, then m (for pane resize etc.)

What the installer creates

File Purpose
/mnt/<pool>/appdata/claude-code/Dockerfile Container image definition (Node 20 + claude-code + tools)
/boot/config/plugins/compose.manager/projects/claude-code/docker-compose.yml Compose config with your selected volumes
/mnt/<pool>/appdata/claude-code/home/ Persistent home directory (Claude config, credentials, projects)
/mnt/<pool>/appdata/claude-code/home/.tmux.conf tmux configuration
/usr/local/bin/claude Host shortcut to attach to the container
/boot/config/go (appended) Persists the shortcut across reboots

Customizing after install

Add/change volumes: Edit the compose file and restart:

nano /boot/config/plugins/compose.manager/projects/claude-code/docker-compose.yml
# Add volumes under the "volumes:" section, e.g.:
#   - /mnt/user/media:/media:ro
cd /boot/config/plugins/compose.manager/projects/claude-code
docker compose up -d

Or re-run the installer to use the interactive volume selector.

Change tmux settings: Edit directly inside the container home:

nano /mnt/<pool>/appdata/claude-code/home/.tmux.conf
# Reload inside tmux: Ctrl+B, then type :source ~/.tmux.conf

Re-running the installer

Running the installer again will:

  • Detect and offer to remove the existing container
  • Migrate any old config layout automatically
  • Regenerate the Dockerfile and docker-compose.yml
  • Rebuild the container image

Your Claude config and credentials (in home/.claude/) are preserved.


Updating Claude Code

Quick update (inside the running container — no rebuild needed):

docker exec -it claude-code npm update -g @anthropic-ai/claude-code

Full rebuild (new base image + latest everything):

cd /boot/config/plugins/compose.manager/projects/claude-code
docker compose build --no-cache
docker compose up -d

Or simply re-run the installer — it will stop the old container and rebuild from scratch.

Note: Your config and credentials are stored in a persistent volume (home/.claude/) and survive all update methods.


Uninstalling

docker stop claude-code && docker rm claude-code
docker rmi claude-code
rm /usr/local/bin/claude
# Remove the "Claude Code shortcut" block from /boot/config/go
# Optionally delete /mnt/<pool>/appdata/claude-code/

Troubleshooting

"Compose Manager not found" Install the Compose Manager plugin from Community Applications (CA tab in the Unraid web UI).

Installer hangs / no prompts appear You're probably using the Unraid web terminal. SSH in instead: ssh root@<your-unraid-ip>

"No storage pool found" Make sure your array is started (Main > Start).

Container starts but claude command shows "not found" The shortcut is created at /usr/local/bin/claude. After a reboot, it's restored from /boot/config/go. If it's missing, re-run the installer.

Want to change mounted volumes? Re-run the installer. It will regenerate the docker-compose.yml with your new selection.

#!/bin/bash
set -e
# Error message for non-interactive environments
terminal_error() {
_ip=$(hostname -I 2>/dev/null | awk '{print $1}')
_host="${_ip:-<your-unraid-ip>}"
echo ""
echo " Error: This installer requires an interactive terminal."
echo " The Unraid web terminal does not support interactive input."
echo ""
echo " Use SSH instead:"
echo " ssh root@${_host}"
echo ""
exit 1
}
# Detect web terminal (ttyd/noVNC/shellinabox) — /dev/tty exists but read hangs
if [ -z "$SSH_TTY" ] && [ -z "$SSH_CONNECTION" ]; then
if pstree -s $$ 2>/dev/null | grep -qiE 'ttyd|shellinabox|novnc|websockify'; then
terminal_error
elif ! tty 2>/dev/null | grep -qE '/dev/(tty[0-9]|pts/)'; then
terminal_error
fi
fi
# Set up interactive input:
# - stdin is a terminal (bash install.sh) → use stdin directly
# - stdin is a pipe (curl | bash) → fall back to /dev/tty
if [ -t 0 ]; then
INPUT_FD=0
prompt() {
read -p "$1" "$2"
}
readchar() {
IFS= read -r -n 1 _ch 2>/dev/null
printf '%s' "$_ch"
}
else
exec 3</dev/tty 2>/dev/null || terminal_error
INPUT_FD=3
prompt() {
read -p "$1" "$2" <&3
}
readchar() {
IFS= read -r -n 1 _ch <&3 2>/dev/null
printf '%s' "$_ch"
}
fi
echo ""
echo " Claude Code on Unraid — Installer"
echo " =================================="
echo ""
# --- Detect storage pool ---
# Unraid 6.12+ allows named pools (cache, nvme, ssd, fast, ...).
# Find the pool that holds appdata, or fall back to first available.
POOL=""
# 1. Check for existing claude-code appdata
for p in /mnt/*/appdata/claude-code; do
[ -d "$p" ] && POOL=$(echo "$p" | cut -d/ -f3) && break
done
# 2. Check for any appdata directory
if [ -z "$POOL" ]; then
for p in /mnt/*/appdata; do
[ -d "$p" ] && POOL=$(echo "$p" | cut -d/ -f3) && break
done
fi
# 3. Fall back to common pool names
if [ -z "$POOL" ]; then
for name in cache nvme ssd fast; do
[ -d "/mnt/$name" ] && POOL="$name" && break
done
fi
# 4. Last resort: first non-special pool
if [ -z "$POOL" ]; then
for p in /mnt/*/; do
[ -d "$p" ] || continue
pname=$(basename "$p")
case "$pname" in user|user0|disk[0-9]*|disks|rootshare|addons|remotes) continue ;; esac
POOL="$pname"
break
done
fi
if [ -z "$POOL" ]; then
echo "Error: No storage pool found. Is the array started?"
exit 1
fi
# --- Prerequisites ---
if ! command -v docker &>/dev/null; then
echo "Error: Docker is not available. Is the Docker service running?"
exit 1
fi
if docker compose version &>/dev/null 2>&1; then
COMPOSE="docker compose"
elif command -v docker-compose &>/dev/null; then
COMPOSE="docker-compose"
else
echo "Error: Docker Compose is not available."
echo "Install the Compose Manager plugin from Community Applications."
exit 1
fi
# --- Handle existing container ---
if docker ps -a --format '{{.Names}}' | grep -q '^claude-code$'; then
echo "An existing claude-code container was found."
prompt "Stop and remove it to continue? [Y/n]: " remove_existing
if [[ "$remove_existing" =~ ^[nN] ]]; then
echo "Aborted."
exit 0
fi
docker stop claude-code 2>/dev/null || true
docker rm claude-code 2>/dev/null || true
echo ""
fi
# --- Workspace & Home ---
WS_DEFAULT="/mnt/${POOL}/appdata/claude-code/workspace"
prompt "Workspace directory [${WS_DEFAULT}]: " WORKSPACE
WORKSPACE="${WORKSPACE:-${WS_DEFAULT}}"
HOME_DEFAULT="/mnt/${POOL}/appdata/claude-code/home"
prompt "Home directory (Claude config & state) [${HOME_DEFAULT}]: " CLAUDE_HOME
CLAUDE_HOME="${CLAUDE_HOME:-${HOME_DEFAULT}}"
# --- Migrate old config ---
OLD_APPDATA="/mnt/${POOL}/appdata/claude-code"
if [ -f "${OLD_APPDATA}/settings.json" ] || \
[ -d "${OLD_APPDATA}/projects" ] || \
[ -d "${OLD_APPDATA}/credentials" ]; then
echo "Found existing Claude config from a previous setup."
echo "The new setup stores everything under ${CLAUDE_HOME}/.claude/"
prompt "Move existing config there now? [Y/n]: " migrate
if [[ ! "$migrate" =~ ^[nN] ]]; then
mkdir -p ${CLAUDE_HOME}/.claude
shopt -s dotglob
for item in ${OLD_APPDATA}/*; do
name=$(basename "$item")
case "$name" in
home|Dockerfile|install.sh|docker-compose.yml|*.md) continue ;;
esac
mv "$item" ${CLAUDE_HOME}/.claude/ 2>/dev/null || true
done
shopt -u dotglob
echo "Done."
fi
echo ""
fi
# --- Volume Selection ---
# Build volume list
VOLUMES=()
VOLUMES+=("${CLAUDE_HOME}:/root:rw")
VOLUMES+=("${WORKSPACE}:/workspace:rw")
# Collect all mountable items: name, source path, category, default state
ITEMS_NAME=()
ITEMS_PATH=()
ITEMS_CAT=()
ITEMS_STATE=() # skip, ro, rw
# Data shares from /mnt/user/
if [ -d "/mnt/user" ]; then
for dir in /mnt/user/*/; do
[ -d "$dir" ] || continue
name=$(basename "$dir")
case "$name" in claude-code) continue ;; esac
case "$name" in
appdata|system|domains|docker|isos) ;; # handled below as system
*)
ITEMS_NAME+=("$name")
ITEMS_PATH+=("/mnt/user/${name}")
ITEMS_CAT+=("data")
ITEMS_STATE+=("skip")
;;
esac
done
fi
# System shares from /mnt/user/
for name in appdata system domains docker isos; do
[ -d "/mnt/user/$name" ] || continue
ITEMS_NAME+=("$name")
ITEMS_PATH+=("/mnt/user/${name}")
ITEMS_CAT+=("system")
ITEMS_STATE+=("skip")
done
# System paths
if [ -d "/boot" ]; then
ITEMS_NAME+=("/boot")
ITEMS_PATH+=("/boot")
ITEMS_CAT+=("paths")
ITEMS_STATE+=("skip")
fi
if [ -d "/var/log" ]; then
ITEMS_NAME+=("/var/log")
ITEMS_PATH+=("/var/log")
ITEMS_CAT+=("paths")
ITEMS_STATE+=("skip")
fi
# Unassigned devices
if [ -d "/mnt/disks" ]; then
for dir in /mnt/disks/*/; do
[ -d "$dir" ] || continue
name=$(basename "$dir")
ITEMS_NAME+=("$name")
ITEMS_PATH+=("/mnt/disks/${name}")
ITEMS_CAT+=("disks")
ITEMS_STATE+=("skip")
done
fi
# Remote mounts
if [ -d "/mnt/remotes" ]; then
for dir in /mnt/remotes/*/; do
[ -d "$dir" ] || continue
name=$(basename "$dir")
ITEMS_NAME+=("$name")
ITEMS_PATH+=("/mnt/remotes/${name}")
ITEMS_CAT+=("remotes")
ITEMS_STATE+=("skip")
done
fi
# Docker socket (special toggle)
DOCKER_ACCESS="yes"
TOTAL=${#ITEMS_NAME[@]}
# Disable set -e for TUI — dd/read/stty can return non-zero harmlessly
set +e
if [ "$TOTAL" -eq 0 ]; then
echo ""
echo " No shares found. You can add volumes manually later."
else
# --- Interactive toggle selector ---
# Pure bash TUI: arrow keys navigate, space cycles skip→ro→rw, enter confirms
# Build display lines with category headers
# Map: display line index → item index (or -1 for header/blank)
DISPLAY_LINES=()
DISPLAY_TEXT=()
LINE_TO_ITEM=()
add_category() {
local cat="$1" header="$2"
local found=false
for i in "${!ITEMS_CAT[@]}"; do
[ "${ITEMS_CAT[$i]}" = "$cat" ] && found=true && break
done
$found || return
# Blank line before header (except first)
if [ ${#DISPLAY_LINES[@]} -gt 0 ]; then
DISPLAY_LINES+=("")
DISPLAY_TEXT+=("")
LINE_TO_ITEM+=(-1)
fi
DISPLAY_LINES+=("header")
DISPLAY_TEXT+=("$header")
LINE_TO_ITEM+=(-1)
for i in "${!ITEMS_CAT[@]}"; do
if [ "${ITEMS_CAT[$i]}" = "$cat" ]; then
DISPLAY_LINES+=("item")
DISPLAY_TEXT+=("${ITEMS_NAME[$i]}")
LINE_TO_ITEM+=("$i")
fi
done
}
add_category "data" " Data shares:"
add_category "system" " System shares (Docker/VM config, system data):"
add_category "paths" " System paths:"
add_category "disks" " Unassigned devices:"
add_category "remotes" " Remote mounts:"
# Add docker socket toggle
DISPLAY_LINES+=("")
DISPLAY_TEXT+=("")
LINE_TO_ITEM+=(-1)
DISPLAY_LINES+=("header")
DISPLAY_TEXT+=(" Other:")
LINE_TO_ITEM+=(-1)
DISPLAY_LINES+=("docker")
DISPLAY_TEXT+=("Docker socket")
LINE_TO_ITEM+=(-2)
NUM_LINES=${#DISPLAY_LINES[@]}
# Find first selectable line
cursor=0
for i in "${!DISPLAY_LINES[@]}"; do
if [ "${DISPLAY_LINES[$i]}" = "item" ] || [ "${DISPLAY_LINES[$i]}" = "docker" ]; then
cursor=$i
break
fi
done
state_label() {
case "$1" in
skip) printf " -- " ;;
ro) printf " ro " ;;
rw) printf " RW " ;;
esac
}
draw_menu() {
# Move cursor to top of menu area and redraw
if [ "${1:-}" = "redraw" ]; then
printf "\033[%dA" "$NUM_LINES"
fi
_i=0
while [ "$_i" -lt "$NUM_LINES" ]; do
_type="${DISPLAY_LINES[$_i]}"
_text="${DISPLAY_TEXT[$_i]}"
_idx="${LINE_TO_ITEM[$_i]}"
printf "\033[2K" # clear line
if [ "$_type" = "header" ]; then
printf "\033[1m%s\033[0m\n" "$_text"
elif [ "$_type" = "item" ]; then
_st="${ITEMS_STATE[$_idx]}"
if [ "$_i" -eq "$cursor" ]; then
printf " \033[7m[%s]\033[0m %s\n" "$(state_label "$_st")" "$_text"
else
printf " [%s] %s\n" "$(state_label "$_st")" "$_text"
fi
elif [ "$_type" = "docker" ]; then
if [ "$DOCKER_ACCESS" = "yes" ]; then _dl=" RW "; else _dl=" -- "; fi
if [ "$_i" -eq "$cursor" ]; then
printf " \033[7m[%s]\033[0m %s (/var/run/docker.sock)\n" "$_dl" "$_text"
else
printf " [%s] %s (/var/run/docker.sock)\n" "$_dl" "$_text"
fi
else
printf "\n"
fi
_i=$((_i + 1))
done
}
# Save terminal state and enable raw mode
old_stty=$(stty -g)
echo ""
echo " Select volumes for Claude Code"
echo " Use arrow keys to navigate, Space to toggle (skip/ro/rw), Enter to confirm"
echo ""
draw_menu "first"
# Ensure terminal is restored on exit/error
trap 'stty "$old_stty" 2>/dev/null' EXIT
if [ "$INPUT_FD" -eq 3 ]; then
stty -echo -icanon min 1 <&3
else
stty -echo -icanon min 1
fi
while true; do
char=$(readchar)
if [ "$char" = $'\033' ]; then
# Escape sequence — read next two chars for arrow keys
char2=$(readchar)
char3=$(readchar)
if [ "$char2" = "[" ]; then
case "$char3" in
A) # Up arrow
_nav=$((cursor - 1))
while [ "$_nav" -ge 0 ]; do
if [ "${DISPLAY_LINES[$_nav]}" = "item" ] || [ "${DISPLAY_LINES[$_nav]}" = "docker" ]; then
cursor=$_nav; break
fi
_nav=$((_nav - 1))
done
;;
B) # Down arrow
_nav=$((cursor + 1))
while [ "$_nav" -lt "$NUM_LINES" ]; do
if [ "${DISPLAY_LINES[$_nav]}" = "item" ] || [ "${DISPLAY_LINES[$_nav]}" = "docker" ]; then
cursor=$_nav; break
fi
_nav=$((_nav + 1))
done
;;
esac
fi
draw_menu "redraw"
elif [ "$char" = " " ]; then
# Space — cycle state
_sel="${LINE_TO_ITEM[$cursor]}"
if [ "$_sel" -eq -2 ]; then
# Docker toggle (on/off)
if [ "$DOCKER_ACCESS" = "yes" ]; then DOCKER_ACCESS="no"; else DOCKER_ACCESS="yes"; fi
elif [ "$_sel" -ge 0 ]; then
case "${ITEMS_STATE[$_sel]}" in
skip) ITEMS_STATE[$_sel]="ro" ;;
ro) ITEMS_STATE[$_sel]="rw" ;;
rw) ITEMS_STATE[$_sel]="skip" ;;
esac
fi
draw_menu "redraw"
elif [ "$char" = "" ] || [ "$char" = $'\n' ]; then
# Enter — confirm selection
break
fi
done
# Restore terminal
stty "$old_stty"
trap - EXIT
# Apply selections to VOLUMES + build summary
USED_TARGETS=()
_summary=""
for _i in "${!ITEMS_NAME[@]}"; do
_st="${ITEMS_STATE[$_i]}"
[ "$_st" = "skip" ] && continue
_src="${ITEMS_PATH[$_i]}"
_name="${ITEMS_NAME[$_i]}"
_cat="${ITEMS_CAT[$_i]}"
case "$_cat" in
paths) _target="/host${_name}" ;; # /boot → /host/boot, /var/log → /host/var/log
*) _target="/${_name}" ;;
esac
# Collision check: ensure target isn't already used
_base_target="$_target"
if printf '%s\n' "${USED_TARGETS[@]}" | grep -qx "$_target" 2>/dev/null; then
_target="/$(echo "${_cat}-${_name}" | tr '/' '-' | sed 's/^-//')"
fi
USED_TARGETS+=("$_target")
VOLUMES+=("${_src}:${_target}:${_st}")
_summary="${_summary} ${_name} (${_st})\n"
done
# Docker socket
if [ "$DOCKER_ACCESS" = "yes" ]; then
VOLUMES+=("/var/run/docker.sock:/var/run/docker.sock:rw")
_summary="${_summary} Docker socket (rw)\n"
fi
# Show confirmation
echo ""
if [ -n "$_summary" ]; then
echo " Selected:"
printf "$_summary"
else
echo " No volumes selected."
fi
fi
# Docker socket (if no TUI was shown)
if [ "$TOTAL" -eq 0 ] && [ "$DOCKER_ACCESS" = "yes" ]; then
VOLUMES+=("/var/run/docker.sock:/var/run/docker.sock:rw")
fi
# Re-enable strict mode
set -e
# --- tmux configuration ---
mkdir -p ${CLAUDE_HOME}
TMUX_CONF="${CLAUDE_HOME}/.tmux.conf"
if [ -f "$TMUX_CONF" ]; then
echo "Existing .tmux.conf found, keeping it."
else
cat > "$TMUX_CONF" << 'TMUXEOF'
set -g history-limit 50000
set -g default-terminal "tmux-256color"
set -g base-index 1
setw -g pane-base-index 1
# Mouse on for scroll wheel, but drag passes through to terminal for native copy
set -g mouse on
# Unbind drag — lets terminal handle text selection natively
unbind -n MouseDrag1Pane
unbind -T copy-mode-vi MouseDrag1Pane
unbind -T copy-mode-vi MouseDragEnd1Pane
unbind -T copy-mode MouseDrag1Pane
unbind -T copy-mode MouseDragEnd1Pane
# Scroll wheel enters copy mode and scrolls tmux history
bind -n WheelUpPane if-shell -F -t = "#{mouse_any_flag}" "send-keys -M" "if -Ft= '#{pane_in_mode}' 'send-keys -M' 'select-pane -t=; copy-mode -e; send-keys -M'"
bind -n WheelDownPane select-pane -t= \; send-keys -M
# Ctrl+B, m to toggle full mouse mode on/off (for pane resize etc.)
bind m set -g mouse \; display "Mouse: #{?mouse,ON,OFF}"
TMUXEOF
fi
# --- Build compose volume block ---
VOLUME_BLOCK=""
for v in "${VOLUMES[@]}"; do
VOLUME_BLOCK="${VOLUME_BLOCK} - ${v}"$'\n'
done
# --- Create files ---
echo ""
echo "Creating files..."
mkdir -p /boot/config/plugins/compose.manager/projects/claude-code
mkdir -p "$WORKSPACE"
mkdir -p "$CLAUDE_HOME"
# Prevent home/ from being sent as build context
cat > ${WORKSPACE}/.dockerignore << 'EOF'
*
!Dockerfile
EOF
cat > ${WORKSPACE}/Dockerfile << 'EOF'
FROM node:20-bookworm
RUN apt-get update && \
apt-get install -y --no-install-recommends \
tmux git curl openssh-client docker.io ripgrep jq htop nano locales && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && \
locale-gen
RUN npm install -g @anthropic-ai/claude-code
ENV LANG=en_US.UTF-8
ENV LC_ALL=en_US.UTF-8
CMD ["tail", "-f", "/dev/null"]
EOF
cat > /boot/config/plugins/compose.manager/projects/claude-code/docker-compose.yml << COMPOSE
services:
claude-code:
build:
context: ${WORKSPACE}
dockerfile: Dockerfile
container_name: claude-code
restart: unless-stopped
hostname: unraid-claude
network_mode: host
privileged: true
volumes:
${VOLUME_BLOCK} working_dir: /workspace
user: root
environment:
- NODE_ENV=development
- SHELL=/bin/bash
- TERM=xterm-256color
- LANG=en_US.UTF-8
- LC_ALL=en_US.UTF-8
labels:
- "com.unraid.docker.managed=composeman"
- "com.unraid.docker.icon=https://cdn.anthropic.com/images/icons/claude.svg"
COMPOSE
# --- Host shortcut ---
cat > /usr/local/bin/claude << 'SCRIPT'
#!/bin/bash
if ! docker ps --format '{{.Names}}' | grep -q '^claude-code$'; then
if docker ps -a --format '{{.Names}}' | grep -q '^claude-code$'; then
echo "Starting claude-code container..."
docker start claude-code >/dev/null
sleep 2
else
echo "claude-code container not found. Run the installer first."
exit 1
fi
fi
docker exec -it claude-code bash -c '
tmux has-session -t cc 2>/dev/null && tmux attach -t cc || tmux new -s cc "claude"
'
SCRIPT
chmod +x /usr/local/bin/claude
# Persist shortcut across reboots
if ! grep -q "Claude Code shortcut" /boot/config/go 2>/dev/null; then
cat >> /boot/config/go << 'BOOT'
# Claude Code shortcut
cat > /usr/local/bin/claude << 'SHORTCUT'
#!/bin/bash
if ! docker ps --format '{{.Names}}' | grep -q '^claude-code$'; then
if docker ps -a --format '{{.Names}}' | grep -q '^claude-code$'; then
echo "Starting claude-code container..."
docker start claude-code >/dev/null
sleep 2
else
echo "claude-code container not found. Run the installer first."
exit 1
fi
fi
docker exec -it claude-code bash -c '
tmux has-session -t cc 2>/dev/null && tmux attach -t cc || tmux new -s cc "claude"
'
SHORTCUT
chmod +x /usr/local/bin/claude
BOOT
fi
# --- Summary ---
echo ""
echo "=== Configuration ==="
echo ""
echo "Home: ${CLAUDE_HOME}"
echo "Workspace: ${WORKSPACE}"
echo ""
echo "Volumes:"
for v in "${VOLUMES[@]}"; do
echo " - $v"
done
echo ""
echo "Files:"
echo " Dockerfile: ${WORKSPACE}/Dockerfile"
echo " Compose: /boot/config/plugins/compose.manager/projects/claude-code/docker-compose.yml"
echo ""
prompt "Build and start now? [Y/n]: " confirm
if [[ "$confirm" =~ ^[nN] ]]; then
echo ""
echo "When ready:"
echo " cd /boot/config/plugins/compose.manager/projects/claude-code"
echo " $COMPOSE build && $COMPOSE up -d"
exit 0
fi
echo ""
echo "Building image (takes ~2 min on first run)..."
cd /boot/config/plugins/compose.manager/projects/claude-code
$COMPOSE build
$COMPOSE up -d
echo ""
echo "=== Done! ==="
echo ""
echo "Type 'claude' to start Claude Code."
echo ""
echo " Detach session: Ctrl+B, D"
echo " Reattach: claude"
echo " Copy: select text normally, Ctrl+Shift+C"
echo " Paste: Ctrl+Shift+V"
echo " Scroll: mouse wheel"
# Close fd 3 if it was opened
exec 3<&- 2>/dev/null || true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment