###2026.07.25.6 - v2.1.3: The "Connect to session ID" field went blank again after every page reload, so reconnecting to the same session meant re-typing or re-picking it from the history list every time. The last id you connected with is now saved to this plugin's own config on the flash share and pre-fills the field automatically. ###2026.07.25.5 - v2.1.2: Replaced the v2.1.1 "wipe the plugin tree before every install" hack with Unraid's actual intended mechanism: each deployed FILE now carries a child SHA256 element with the hash of its real on-disk content. Unraid's installer already supports this -- if a deployed file's hash doesn't match, it deletes and redeploys it; if it matches, it's left alone untouched. Same end result (Update now actually redeploys changed files, for every user, not just this one install) but doesn't touch files that didn't change, and doesn't briefly remove the whole plugin tree during every future update. Verified by replicating the installer's own file_exists+SHA256+INLINE/base64 logic against a sandbox: stale files get unlinked and rewritten, unchanged files get skipped, a fresh install writes everything. - v2.1.2: Fixed two identically-labeled "View live logs" links on the Settings page (one for the install/update log, one buried inside the Remote Control box for its own supervisor log) that made it impossible to tell which was which. Both now live together under the "Logs" section, labeled "Install / Update log" and "Remote Control log". ###2026.07.25.4 - v2.1.1: Fixed "Update" on the Plugins page (and any repeat `plugin install`) silently deploying nothing new. Unraid's own installer only writes a FILE's target path if it doesn't already exist, and none of this plugin's FILE elements carry a checksum to force a refresh -- so every previous update only ever re-registered the .plg definition itself while every actual script/page/icon stayed frozen at whatever was first installed, no matter the version shown. Added a cleanup step (a Run-only FILE with no Name=, which isn't subject to that existence check) that removes the deployed plugin tree before every install/update so the files below are always redeployed fresh. Does not touch settings, session history, or auth data under /boot/config/plugins/claude-code/. If you're reading this from an older install, one more Update now fully applies -- this fix ships in the same install run it fixes. ###2026.07.25.3 - v2.1.0: Fixed Remote Control silently getting stuck on a session archived on the claude.ai side. Resuming an archived session doesn't fail the way "no session yet" does -- it exits with "Session failed: Process exited with error" every single time, so the supervisor was retrying the same dead session forever instead of recovering. It now gives up after 3 consecutive failures like that, starts a fresh session automatically, and shows a clearly worded notice on the Settings page so you know it happened (bilingual). - v2.1.0: Added a session history: every session id Remote Control ever connects to (fresh or resumed) is now logged to the flash share with its timestamp and working directory, and shown as a table on the Settings page. - v2.1.0: Added a "Connect to session ID" field on the Settings page (with the history above as autocomplete suggestions) so you can stop the current session and reconnect to any specific past session id -- including one pasted from claude.ai/code -- instead of always resuming only the most recent one. Falls back to a fresh session automatically if that id can't be resumed either. ###2026.07.25.2 - v2.0.0: Added Remote Control. Runs "claude remote-control" as a supervised background process (started/stopped only from the Settings page, never at boot) so you can drive a Claude Code session on this NAS from claude.ai/code or the Claude mobile app -- outbound HTTPS only, no inbound ports. Always resumes the same ongoing session in its configured directory (continue flag), falling back to a fresh session only the first time. Requires a claude.ai OAuth login (Pro/Max/Team/Enterprise) -- API key auth does not support it. Verified live end to end on the fork maintainer's own NAS: session resumed correctly across a stop/start cycle, and the same session ID persisted. ###2026.07.25.1 - v1.3.1: Fixed the Update action itself: `claude update` downloads the new version into ~/.local/share/claude/versions/ but only repoints ~/.local/bin/claude at it when that file is a symlink the native installer manages -- on this plugin it's a plain copy restored from the appdata cache on every boot, so the CLI left the live binary untouched (confirmed live on the fork maintainer's own NAS: "claude update" reported success but `claude --version` kept showing the old version). update-claude.sh now finds the newest file under versions/ and applies it to ~/.local/bin/claude itself before re-caching. ###2026.07.25 - v1.3.0: Fixed the "View live logs" link never appearing (it was hidden until /var/log/claude-code-install.log existed, which was only ever created on an actual Unraid boot). The link is now always shown -- Unraid's own openTerminal() already handles a not-yet-existing file gracefully with a wait screen. - v1.3.0: Added a real "Update Claude Code" action. Previously, install_claude_code() in install-claude.sh is a no-op once `claude` already works, so the "Reinstall" button could never actually fetch a newer Claude Code release -- clicking it while already installed just confirmed the current version and did nothing else. The new Update button runs the CLI's own `claude update` and then re-caches the resulting binary into APPDATA_PATH/bin, since restore_cached_binary() on every boot would otherwise silently revert any update back to whatever was cached at first install. - v1.3.0: Reinstall and Update now both append their output to /var/log/claude-code-install.log (via new scripts/reinstall-and-log.sh and scripts/update-claude.sh) so the live log viewer has something to show right after using either button, not just after a reboot. ###2026.07.24.4 - v1.2.1: Fixed the "no log yet" help text on the Settings page: it claimed clicking Reinstall would create the log, but Reinstall calls install-claude.sh directly, not rc.claude-code (the only script that writes to the log) — only an actual reboot does. ###2026.07.24.3 - v1.2.0: Plugins tab now shows "Claude Code Remote Control" with a real description (bilingual) instead of the bare word "claude-code". Unraid renders plugins/claude-code/README.md as that description, so this fork now deploys a short README.md into the plugin's own emhttp folder (separate from this repo's top-level README.md, which stays as-is). Also updated the Settings page Title= and the plugin's DESCRIPTION to match. - v1.2.0: Added a "Logs" section to the Settings page with a "View live logs" link that streams /var/log/claude-code-install.log in real time, using Unraid's own native openTerminal('log', ...) mechanism (same one Docker/VMs/NUT use) — no extra scripts needed, unlike the Zabbix Agent fork's syslog viewer, since Claude Code already always writes to a dedicated log file. ###2026.07.24.2 - v1.1.0: author is now just "Nebur692" (was "brianpugh (fork: Nebur692)"). - v1.1.0: added launch="Settings/claude-code" so clicking the plugin row on the Plugins tab jumps straight to its Settings page, same as the Zabbix Agent fork. - v1.1.0: fixed the Plugins-tab icon. Unraid's Plugins tab does NOT use the icon= attribute the way you'd expect for a remote URL: ShowPlugins.php treats a .png icon= value as a local filename under plugins/NAME/images/ or plugins/NAME/, never as a URL — so the old icon="https://...png" always fell through to a generic fallback icon. Removed icon= entirely (matching the Zabbix Agent fork) and moved the bundled icon from icons/claude-code.png to images/claude-code.png, which is the exact path icon($name) in PluginHelpers.php looks for. Now picked up automatically with no icon= attribute needed. - v1.1.0: Settings page restyled to match the Zabbix Agent fork's look (bordered .claude-box sections, a compact status bar, blockquote.inline_help notes) instead of the original ad-hoc table/div layout. ###2026.07.24.1 - v1.0.2: Fix the base64 icon fix itself: Type="base64" must be an attribute of the outer FILE element (that's what Unraid's plugin script actually reads), not of the inner INLINE element. In v1.0.0/v1.0.1 the icon was written verbatim as base64 text instead of being decoded to a real PNG. ###2026.07.24 - v1.0.1: Settings page (Settings > Utilities > Claude Code) is now bilingual, auto-detecting English/Spanish from Unraid's webGUI language setting (dynamix.cfg locale=es_*), same pattern as the author's Zabbix Agent fork. - v1.0.0: Fork of brianpugh/unraid-claude-code, renamed "Claude Code Remote Control Unraid Plugin" - Fix: icon is now embedded as inline base64 instead of fetched over the network at boot. Previously, if that download failed (e.g. network not ready yet right after a reboot), Unraid would abort and permanently banish the whole plugin to /boot/config/plugins-error, silently disabling it on every future boot until manually reinstalled. ###2026.03.06 - Initial release - Installs Claude Code CLI - Persistent authentication via user-configurable appdata folder Claude Code Remote Control - AI-powered coding assistant CLI from Anthropic, installed on Unraid with persistent authentication across reboots. Optionally drive a Claude Code session on this NAS from claude.ai/code or the Claude mobile app via Anthropic's Remote Control feature. mkdir -p /boot/config/plugins/claude-code mkdir -p /usr/local/emhttp/plugins/claude-code/scripts mkdir -p /usr/local/emhttp/plugins/claude-code/images 3995378ba18f82d3e0cfca9f4c6dad09c9dd713a9a734d19584725c30fa37536 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 678bf46ace835698ab0137320129c7597d67cdf5c2752c0dcbe185faa0e95920 Utilities > Claude Code. --- Asistente de programación con IA de Anthropic, instalado en Unraid con autenticación persistente entre reinicios. Ejecuta `claude` desde la terminal de Unraid, o controla una sesión desde claude.ai/code o la app móvil de Claude con Control Remoto. Configúralo y mira los logs en vivo desde Settings > Utilities > Claude Code. ]]> /boot/config/plugins/claude-code/claude-code.cfg fi ]]> defa8ffc1222386116ebc54c9ead66fd56a285441f134549c542a7129b550ed3 /dev/null | cut -d'"' -f2) if [ -n "$DOCKER_APPDATA" ] && [ -d "$(dirname "$DOCKER_APPDATA")" ]; then APPDATA_PATH="${DOCKER_APPDATA%/}/claude-code" echo "Detected from Docker config: $APPDATA_PATH" fi fi # Method 2: Look for existing appdata directories if [ -z "$APPDATA_PATH" ]; then for candidate in /mnt/user/appdata /mnt/disks/*/appdata /mnt/cache/appdata; do if [ -d "$candidate" ]; then APPDATA_PATH="$candidate/claude-code" echo "Found appdata directory: $APPDATA_PATH" break fi done fi # Fallback if [ -z "$APPDATA_PATH" ]; then APPDATA_PATH="/mnt/user/appdata/claude-code" echo "Using default: $APPDATA_PATH" fi fi CACHED_BINARY="$APPDATA_PATH/bin/claude" echo "=== Claude Code Installation ===" ensure_symlink() { # Always ensure /usr/local/bin/claude symlink exists so claude is on # PATH for interactive shells (which don't have ~/.local/bin in PATH) if [ -x /usr/local/bin/claude ]; then return 0 fi for candidate in /root/.local/bin/claude /root/.claude/local/bin/claude; do if [ -x "$candidate" ]; then ln -sf "$candidate" /usr/local/bin/claude echo "Symlinked $candidate -> /usr/local/bin/claude" return 0 fi done return 1 } restore_cached_binary() { # Restore binary from appdata cache (avoids re-downloading on every boot) if [ -f "$CACHED_BINARY" ]; then echo "Restoring Claude Code from cache..." mkdir -p /root/.local/bin cp "$CACHED_BINARY" /root/.local/bin/claude chmod +x /root/.local/bin/claude return 0 fi return 1 } cache_binary() { # Cache the binary to appdata so it survives reboots if [ -x /root/.local/bin/claude ]; then mkdir -p "$APPDATA_PATH/bin" cp /root/.local/bin/claude "$CACHED_BINARY" echo "Cached Claude binary for future boots." fi } install_claude_code() { # Check if claude works (not just exists) local ver ver=$(claude --version 2>/dev/null) if [ -n "$ver" ]; then echo "Claude Code already installed: $ver" ensure_symlink return 0 fi # Try restoring from cache first (fast, no network needed) if restore_cached_binary; then ensure_symlink ver=$(claude --version 2>/dev/null) if [ -n "$ver" ]; then echo "Claude Code restored from cache: $ver" return 0 fi echo "Cached binary didn't work, falling back to installer..." fi # Full install via native installer echo "Installing Claude Code via native installer..." local installer installer=$(mktemp /tmp/claude-install.XXXXXX) if ! curl -fsSL https://claude.ai/install.sh -o "$installer"; then echo "Failed to download installer." rm -f "$installer" return 1 fi if ! bash "$installer"; then echo "Installer script failed." rm -f "$installer" return 1 fi rm -f "$installer" ensure_symlink cache_binary if command -v claude &> /dev/null; then echo "Claude Code installed successfully!" else echo "WARNING: Claude Code installer completed but 'claude' not found in PATH" return 1 fi } wait_for_appdata_mount() { # Determine the mount point for our appdata path local wait_mount="/mnt/user" case "$APPDATA_PATH" in /mnt/disks/*) # Unassigned Devices: extract mount like /mnt/disks/vms wait_mount=$(echo "$APPDATA_PATH" | cut -d/ -f1-4) ;; /mnt/cache/*) wait_mount="/mnt/cache" ;; esac local max_wait=120 local waited=0 echo "Waiting for $wait_mount..." while [ $waited -lt $max_wait ]; do if mountpoint -q "$wait_mount" 2>/dev/null; then echo "$wait_mount is available." return 0 fi sleep 5 waited=$((waited + 5)) echo "Waiting for $wait_mount... (${waited}s/${max_wait}s)" done echo "WARNING: $wait_mount not available after ${max_wait}s" return 1 } setup_persistence() { echo "Setting up persistent storage..." echo "Appdata path: $APPDATA_PATH" # Check if appdata mount is available if ! wait_for_appdata_mount; then echo "WARNING: Appdata mount not available. Persistence will be configured later." return 0 fi # Early exit if symlink is already correct if [ -L "$CLAUDE_CONFIG_RUNTIME" ] && [ "$(readlink "$CLAUDE_CONFIG_RUNTIME")" = "$APPDATA_PATH/.claude" ]; then echo "Persistence already configured." return 0 fi # Create appdata directory if it doesn't exist mkdir -p "$APPDATA_PATH" # If runtime config exists as a real directory (not symlink), migrate it if [ -d "$CLAUDE_CONFIG_RUNTIME" ] && [ ! -L "$CLAUDE_CONFIG_RUNTIME" ]; then echo "Migrating existing Claude config to appdata..." if [ -d "$APPDATA_PATH/.claude" ]; then # Merge: appdata takes precedence, but copy any missing files from runtime # Use -a (archive) -n (no clobber); dotglob ensures hidden files are included shopt -s dotglob cp -an "$CLAUDE_CONFIG_RUNTIME"/* "$APPDATA_PATH/.claude/" 2>/dev/null || true shopt -u dotglob else mv "$CLAUDE_CONFIG_RUNTIME" "$APPDATA_PATH/.claude" fi rm -rf "$CLAUDE_CONFIG_RUNTIME" fi # Create .claude directory in appdata if needed mkdir -p "$APPDATA_PATH/.claude" # Create symlink (ln -sf handles removing existing symlink) ln -sf "$APPDATA_PATH/.claude" "$CLAUDE_CONFIG_RUNTIME" # Save auto-detected path to config so it's consistent on future boots if [ "$AUTO_DETECTED" = true ]; then echo "APPDATA_PATH=\"$APPDATA_PATH\"" > "$PLUGIN_CFG" echo "Saved appdata path to config." fi echo "Persistence configured: $CLAUDE_CONFIG_RUNTIME -> $APPDATA_PATH/.claude" } setup_claude_json() { # /root/.claude.json is a separate config file on tmpfs, lost on reboot. # Persist it by symlinking to appdata. local src="/root/.claude.json" local dest="$APPDATA_PATH/.claude.json" # If real file exists (not symlink), move it to appdata if [ -f "$src" ] && [ ! -L "$src" ]; then mv "$src" "$dest" fi # Create symlink if appdata copy exists if [ -f "$dest" ] && [ ! -L "$src" ]; then ln -sf "$dest" "$src" echo "Linked .claude.json to appdata." fi } # Main # Set up persistence first so the native installer writes into appdata # (the installer writes to ~/.claude/local/ which follows our symlink) setup_persistence setup_claude_json if ! install_claude_code; then echo "WARNING: Failed to install Claude Code" echo "You can retry manually: /usr/local/emhttp/plugins/claude-code/scripts/install-claude.sh" echo "Or install manually: curl -fsSL https://claude.ai/install.sh | bash" fi echo "" if command -v claude &> /dev/null; then echo "=== Installation Complete ===" echo "Run 'claude' to start using Claude Code." echo "Your authentication will persist in: $APPDATA_PATH" else echo "=== Installation Incomplete ===" echo "Claude Code is not yet available." echo "Check /var/log/claude-code-install.log for details." echo "" echo "Troubleshooting:" echo " 1. Make sure you have network connectivity" echo " 2. Try running: /usr/local/emhttp/plugins/claude-code/scripts/install-claude.sh" fi ]]> bebae0b077321bc2f106e5f91aad56d1ba79975b4c0c717b27b2f3a1dff3a5e2 > "$LOGFILE" /usr/local/emhttp/plugins/claude-code/scripts/install-claude.sh 2>&1 | tee -a "$LOGFILE" ]]> d16b0307c79bba29bf255593954b4ff05305215e32079da311123a24ac68ec9a >(tee -a "$LOGFILE") 2>&1 echo "=== Manual Update $(date) ===" PLUGIN_CFG="/boot/config/plugins/claude-code/claude-code.cfg" [ -f "$PLUGIN_CFG" ] && source "$PLUGIN_CFG" RUNTIME_BIN="/root/.local/bin/claude" VERSIONS_DIR="/root/.local/share/claude/versions" if ! command -v claude &> /dev/null; then echo "Claude Code is not installed yet -- use Reinstall first." exit 1 fi echo "Current version: $(claude --version 2>/dev/null)" echo "Checking for updates..." claude update # `claude update` downloads new versions into $VERSIONS_DIR, but only # repoints RUNTIME_BIN at the newest one if RUNTIME_BIN is a symlink it # manages itself. On this plugin RUNTIME_BIN is a plain copy (restored # from the appdata cache on every boot), so the CLI leaves it untouched # and prints a warning -- the newly downloaded version has to be applied # by hand below, or "Now running" would still show the old version. if [ -d "$VERSIONS_DIR" ]; then latest=$(ls -1 "$VERSIONS_DIR" 2>/dev/null | sort -V | tail -1) if [ -n "$latest" ] && [ -x "$VERSIONS_DIR/$latest" ]; then cp "$VERSIONS_DIR/$latest" "$RUNTIME_BIN" chmod +x "$RUNTIME_BIN" echo "Applied version $latest to $RUNTIME_BIN." fi fi echo "Now running: $(claude --version 2>/dev/null)" if [ -n "$APPDATA_PATH" ]; then if [ -x "$RUNTIME_BIN" ]; then mkdir -p "$APPDATA_PATH/bin" cp "$RUNTIME_BIN" "$APPDATA_PATH/bin/claude" echo "Cached the updated binary to $APPDATA_PATH/bin -- this version will survive the next reboot." fi else echo "WARNING: APPDATA_PATH not set, could not cache the update -- it may be reverted on next reboot." fi ]]> 0d38a299e676850b43f09343d9ba928b61dae9698d4b26fba3e4b198f51670e6 /dev/null; then sed -i "s|^${key}=.*|${key}=\"${value}\"|" "$PLUGIN_CFG" else echo "${key}=\"${value}\"" >> "$PLUGIN_CFG" fi } # Pulls the session id claude printed a "claude.ai/code/session_..." link # for out of this attempt's output and appends it to the history file, # skipping if it's the same id already at the top (the same session shows # up in output repeatedly while its connection status updates in place). record_session() { local output="$1" rc_dir="$2" sid sid=$(echo "$output" | grep -oE 'session_[A-Za-z0-9]+' | tail -1) [ -z "$sid" ] && return mkdir -p "$(dirname "$HISTORY_FILE")" if [ ! -f "$HISTORY_FILE" ] || ! tail -1 "$HISTORY_FILE" | grep -q " $sid "; then echo "$(date -Iseconds) $sid $rc_dir" >> "$HISTORY_FILE" fi } run_fresh() { local rc_dir="$1" echo "=== Starting a fresh session in $rc_dir: $(date) ===" >> "$LOGFILE" local output output=$(claude remote-control --remote-control-session-name-prefix "unraid" 2>&1 < /dev/null | tee -a "$LOGFILE") record_session "$output" "$rc_dir" } do_run() { local rc_dir output pinned_id fail_count=0 rc_dir=$(get_rc_dir) mkdir -p "$rc_dir" echo "=== Remote Control supervisor started $(date) (dir: $rc_dir) ===" >> "$LOGFILE" while [ -f "$ENABLED_FLAG" ]; do cd "$rc_dir" || break if [ -f "$PINFILE" ]; then pinned_id=$(cat "$PINFILE") rm -f "$PINFILE" echo "=== Connecting to requested session id $pinned_id: $(date) ===" >> "$LOGFILE" output=$(claude remote-control --session-id "$pinned_id" --remote-control-session-name-prefix "unraid" 2>&1 < /dev/null | tee -a "$LOGFILE") record_session "$output" "$rc_dir" if echo "$output" | grep -q "Session failed: Process exited with error"; then echo "=== ARCHIVED_SESSION_FALLBACK: requested session $pinned_id could not be resumed (likely archived or ended) -- starting a new session instead: $(date) ===" >> "$LOGFILE" run_fresh "$rc_dir" fi fail_count=0 else output=$(claude remote-control --continue --remote-control-session-name-prefix "unraid" 2>&1 < /dev/null | tee -a "$LOGFILE") record_session "$output" "$rc_dir" if echo "$output" | grep -q "No recent session found"; then fail_count=0 run_fresh "$rc_dir" elif echo "$output" | grep -q "Session failed: Process exited with error"; then fail_count=$((fail_count + 1)) if [ "$fail_count" -ge "$ARCHIVED_FAIL_THRESHOLD" ]; then echo "=== ARCHIVED_SESSION_FALLBACK: previous session in $rc_dir failed to resume $fail_count times in a row (likely archived or ended) -- starting a new session instead: $(date) ===" >> "$LOGFILE" fail_count=0 run_fresh "$rc_dir" fi else fail_count=0 fi fi [ -f "$ENABLED_FLAG" ] || break echo "=== Remote Control exited, restarting in 5s: $(date) ===" >> "$LOGFILE" sleep 5 done echo "=== Remote Control supervisor stopped $(date) ===" >> "$LOGFILE" rm -f "$PIDFILE" } stop_running() { rm -f "$ENABLED_FLAG" if [ -f "$PIDFILE" ]; then kill "$(cat "$PIDFILE")" 2>/dev/null rm -f "$PIDFILE" fi pkill -f "claude remote-control" 2>/dev/null } case "$1" in start) if [ -f "$PIDFILE" ] && [ -d "/proc/$(cat "$PIDFILE")" ]; then echo "Remote Control is already running." exit 0 fi rm -f "$PINFILE" touch "$ENABLED_FLAG" nohup "$0" _run > /dev/null 2>&1 & echo $! > "$PIDFILE" echo "Remote Control starting in directory: $(get_rc_dir)" echo "Use 'View live logs' below for the session URL / QR code." ;; _run) do_run ;; connect) sid="$2" if ! echo "$sid" | grep -qE '^session_[A-Za-z0-9]+$'; then echo "Usage: $0 connect (expected a session_... id)" exit 1 fi save_cfg_var "LAST_SESSION_ID" "$sid" stop_running echo "$sid" > "$PINFILE" touch "$ENABLED_FLAG" nohup "$0" _run > /dev/null 2>&1 & echo $! > "$PIDFILE" echo "Remote Control connecting to session: $sid" echo "Use 'View live logs' below for status." ;; stop) stop_running rm -f "$PINFILE" echo "=== Remote Control stopped by user $(date) ===" >> "$LOGFILE" echo "Remote Control stopped." ;; status) if [ -f "$PIDFILE" ] && [ -d "/proc/$(cat "$PIDFILE")" ]; then echo "running" else echo "stopped" fi ;; *) echo "Usage: $0 {start|stop|status|connect }" exit 1 ;; esac ]]> e6dfdbad58b1f5b212d30247d3ce2f78c044431b299ff41a17642b29a88a3788 /dev/null && echo yes || echo no")) === "yes"; // Check if claude is installed $claude_installed = trim(shell_exec("which claude 2>/dev/null")) !== ""; $claude_version = $claude_installed ? htmlspecialchars(trim(shell_exec("claude --version 2>/dev/null")) ?: "Unknown") : ""; // Check symlink status $symlink_target = is_link("/root/.claude") ? readlink("/root/.claude") : null; $symlink_ok = $symlink_target === "$appdata_path/.claude"; // Install/startup log (written by rc.claude-code on every boot) $log_name = "claude-code-install.log"; $log_exists = file_exists("/var/log/$log_name"); // Remote Control status/config $rc_dir_cfg = $cfg['REMOTE_CONTROL_DIR'] ?? ''; $rc_dir = $rc_dir_cfg !== '' ? $rc_dir_cfg : '/root/claude-remote-control'; // Last session id passed to "connect" (saved by remote-control.sh itself, not // this form) -- pre-fills the field below so it doesn't go blank again after // a page reload and you don't have to dig it back out of the history table. $last_session_id = $cfg['LAST_SESSION_ID'] ?? ''; $rc_pidfile = '/var/run/claude-remote-control.pid'; $rc_running = false; if (file_exists($rc_pidfile)) { $rc_pid = trim(@file_get_contents($rc_pidfile)); $rc_running = $rc_pid !== '' && file_exists("/proc/$rc_pid"); } $rc_log_name = 'claude-code-remote-control.log'; $rc_log_exists = file_exists("/var/log/$rc_log_name"); $rc_session_url = ''; $rc_archived_notice = ''; if ($rc_log_exists) { $rc_log_tail = shell_exec("tail -n 300 /var/log/$rc_log_name 2>/dev/null"); if ($rc_log_tail && preg_match_all('#https://claude\.ai/code[^\s]*#', $rc_log_tail, $rc_matches) && !empty($rc_matches[0])) { $rc_session_url = end($rc_matches[0]); } if ($rc_log_tail && preg_match_all('/^=== ARCHIVED_SESSION_FALLBACK:.*$/m', $rc_log_tail, $rc_afb)) { $rc_archived_notice = end($rc_afb[0]); } } // Session ID history (persisted on the flash share so it survives reboots), // written by remote-control.sh's record_session() every time it connects. // Format per line: " ". $rc_history_file = '/boot/config/plugins/claude-code/session-history.log'; $rc_history = []; if (file_exists($rc_history_file)) { $rc_lines = array_filter(array_map('trim', file($rc_history_file))); foreach (array_reverse($rc_lines) as $rc_line) { if (preg_match('/^(\S+)\s+(session_\S+)\s+(.*)$/', $rc_line, $rc_m)) { $rc_sid = $rc_m[2]; if (!isset($rc_history[$rc_sid])) { $rc_history[$rc_sid] = ['time' => $rc_m[1], 'dir' => $rc_m[3]]; } } } } ?>

Claude Code: · : · : · Symlink:


$rc_info): ?>
$rc_info): ?>

claude

  1. claude

]]>
7eec70b77833d052a834c5c241972955e63b255c0eaf302e13511b2d77c94bbd /dev/null || ping -c 1 -W 2 1.1.1.1 &>/dev/null; then echo "Network is available." return 0 fi sleep 5 waited=$((waited + 5)) echo "Waiting for network... (${waited}s/${max_wait}s)" done echo "WARNING: Network not available after ${max_wait}s" return 1 } wait_for_array() { local max_wait=300 local waited=0 echo "Waiting for array..." while [ $waited -lt $max_wait ]; do if mountpoint -q /mnt/user 2>/dev/null; then echo "Array is available." return 0 fi sleep 5 waited=$((waited + 5)) echo "Waiting for array... (${waited}s/${max_wait}s)" done echo "WARNING: Array not available after ${max_wait}s" return 1 } do_install() { echo "=== Claude Code Startup $(date) ===" > "$LOGFILE" # Wait for dependencies wait_for_network >> "$LOGFILE" 2>&1 wait_for_array >> "$LOGFILE" 2>&1 # Run the actual install "$INSTALL_SCRIPT" >> "$LOGFILE" 2>&1 } case "$1" in start) echo "Starting Claude Code..." # Run in background to not block boot nohup "$0" _run > /dev/null 2>&1 & ;; _run) export PATH="/root/.local/bin:/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin" export HOME='/root' do_install ;; stop) echo "Stopping Claude Code..." ;; restart) "$0" stop "$0" start ;; *) echo "Usage: $0 {start|stop|restart}" exit 1 ;; esac ]]> /dev/null; then echo "" >> "$GO_FILE" echo "# Start Claude Code" >> "$GO_FILE" echo "$STARTUP_CMD" >> "$GO_FILE" # Run install now (only on first-time plugin install, not on boot) /usr/local/emhttp/plugins/claude-code/scripts/install-claude.sh echo "" echo "-----------------------------------------------------------" echo " Claude Code plugin v2026.07.25.6 has been installed!" echo "" echo " To use: Open terminal and run: claude" echo " Web UI: Settings -> Utilities -> Claude Code" echo "-----------------------------------------------------------" echo "" fi ]]> /dev/null || true sed -i '/rc.claude-code/d' /boot/config/go 2>/dev/null || true # Remove rc.d script rm -f /etc/rc.d/rc.claude-code # Remove Claude Code binary (native install locations) rm -rf /root/.local/bin/claude 2>/dev/null || true CLAUDE_REAL_DIR=$(readlink -f /root/.claude 2>/dev/null || echo "/root/.claude") rm -rf "$CLAUDE_REAL_DIR/local" 2>/dev/null || true # Remove config symlink rm -f /root/.claude 2>/dev/null || true # Remove plugin runtime files rm -rf /usr/local/emhttp/plugins/claude-code rm -f /usr/local/bin/claude # Note: We keep config and appdata for potential reinstall echo "" echo "Claude Code removed." echo "Config preserved at: /boot/config/plugins/claude-code/" echo "Auth data preserved at your appdata location." echo "Delete manually if you want a complete removal." ]]>