From da6737c5baf46399e0fa41470af14de7a38fa493 Mon Sep 17 00:00:00 2001 From: SAGE Date: Thu, 29 Jan 2026 10:02:56 +0800 Subject: [PATCH] Easy install Claude Code --- Dockerfile | 77 +++++++++++++--- README.md | 94 +++++++++++++++++++ railway-entrypoint.sh | 203 +++++++++++++++++++++++++++++++++--------- railway.toml | 13 +++ 4 files changed, 331 insertions(+), 56 deletions(-) create mode 100644 README.md create mode 100644 railway.toml diff --git a/Dockerfile b/Dockerfile index 380af387f..3eecd4fbe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,46 +1,97 @@ -# Custom code-server image for Railway with persistent Node.js and extensions +# ============================================================================ +# VSCode Cloud IDE - Claude Code & Node.js Ready +# Browser-based VSCode with persistent extensions and Node.js +# https://github.com/sphinxcode/code-server +# ============================================================================ + FROM codercom/code-server:latest USER root -# Install Node.js 20 LTS +# Install FALLBACK Node.js and essential development tools +# Volume-installed versions take priority at runtime via PATH ordering RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ - && apt-get install -y nodejs \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + nodejs \ + git \ + curl \ + wget \ + unzip \ + jq \ + htop \ + vim \ + nano \ + ripgrep \ && npm install -g npm@latest \ + && apt-get clean \ && rm -rf /var/lib/apt/lists/* -# Set HOME to /home/coder (the volume mount point on Railway) -# This ensures all user data goes to the persistent volume +# ============================================================================ +# PERSISTENCE CONFIGURATION +# Default paths - can be overridden via CODER_HOME environment variable +# ============================================================================ + ENV HOME=/home/coder ENV USER=coder -# Set XDG directories to use the persistent volume +# XDG Base Directory Specification ENV XDG_DATA_HOME=/home/coder/.local/share ENV XDG_CONFIG_HOME=/home/coder/.config ENV XDG_CACHE_HOME=/home/coder/.cache ENV XDG_STATE_HOME=/home/coder/.local/state -# Ensure Node.js is in PATH and add code-server CLI -ENV PATH="/usr/bin:/usr/local/bin:/home/coder/.local/bin:/home/coder/.local/node/bin:/usr/lib/code-server/lib/vscode/bin/remote-cli:${PATH}" +# ============================================================================ +# PATH PRIORITY ORDER (volume paths FIRST, image paths LAST) +# This ensures user-installed tools on the volume take precedence +# ============================================================================ -# Set entrypoint.d to the persistent volume for custom startup scripts +ENV PATH="/home/coder/.local/node/bin:/home/coder/.claude/local:/home/coder/.local/bin:/home/coder/node_modules/.bin:/usr/local/bin:/usr/bin:/usr/lib/code-server/lib/vscode/bin/remote-cli:${PATH}" + +# Custom startup scripts directory (on volume) ENV ENTRYPOINTD=/home/coder/entrypoint.d -# Create necessary directories -RUN mkdir -p /home/coder/.local/share \ +# ============================================================================ +# DIRECTORY SETUP +# ============================================================================ + +RUN mkdir -p \ + /home/coder/.local/share \ /home/coder/.config \ /home/coder/.cache \ /home/coder/.local/state \ /home/coder/.local/bin \ + /home/coder/.local/node \ + /home/coder/.claude \ /home/coder/entrypoint.d \ + /home/coder/workspace \ && chown -R 1000:1000 /home/coder # Copy custom entrypoint COPY railway-entrypoint.sh /usr/bin/railway-entrypoint.sh RUN chmod +x /usr/bin/railway-entrypoint.sh +# ============================================================================ +# FALLBACK CLAUDE CODE CLI INSTALLATION +# Installed to /usr/local/bin as fallback - volume version takes priority +# ============================================================================ + +RUN curl -fsSL https://claude.ai/install.sh | bash \ + && if [ -f /root/.claude/local/claude ]; then \ + mv /root/.claude/local/claude /usr/local/bin/claude; \ + chmod +x /usr/local/bin/claude; \ + fi \ + && rm -rf /root/.claude + +# ============================================================================ +# RUNTIME +# Note: We run as root for compatibility with existing volumes +# The entrypoint creates symlinks to ensure persistence +# ============================================================================ + +WORKDIR /home/coder/workspace EXPOSE 8080 -# Use our custom entrypoint that handles Railway's root user ENTRYPOINT ["/usr/bin/railway-entrypoint.sh"] -CMD ["--bind-addr", "0.0.0.0:8080", "."] +CMD ["--bind-addr", "0.0.0.0:8080", "/home/coder/workspace"] + diff --git a/README.md b/README.md new file mode 100644 index 000000000..a54ec261d --- /dev/null +++ b/README.md @@ -0,0 +1,94 @@ +# VSCode Cloud IDE + +**Browser-based VSCode with Claude Code & Node.js** + +[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/template/TEMPLATE_ID) + +Cloud IDE with persistent extensions, settings, and tools. Zero configuration. + +--- + +## Features + +- **Claude Code** & **Node.js 20** pre-installed +- Extensions persist across redeployments +- Volume-installed tools take priority (update when YOU want) +- Custom startup scripts supported + +--- + +## Quick Start + +```bash +# Claude Code ready to use +claude + +# Node.js ready to use +node --version +npm --version +``` + +--- + +## How Updates Work + +| Component | Behavior | +|-----------|----------| +| **Volume tools** | YOU control updates. Install to `~/.local/node/` or `~/.claude/local/` | +| **Image tools** | Auto-update on redeploy (fallback if no volume version) | +| **Extensions** | Never reset (persisted on volume) | + +The startup logs show `[volume]` or `[image]` next to each tool. + +--- + +## Environment Variables + +| Variable | Required | Default | Description | +|----------|----------|---------|-------------| +| `PASSWORD` | Yes | - | Login password | +| `CODER_HOME` | No | `/home/coder` | Volume mount path | + +--- + +## Custom Volume Path + +If you change the volume mount location: + +``` +CODER_HOME=/your/volume/path +``` + +Everything adapts automatically. + +--- + +## Custom Startup Scripts + +Add to `$CODER_HOME/entrypoint.d/`: + +```bash +#!/bin/bash +git config --global user.name "Your Name" +``` + +Make executable: `chmod +x script.sh` + +--- + +## Troubleshooting + +| Issue | Check | +|-------|-------| +| Wrong Node version | Look for `[volume]` vs `[image]` in logs | +| Extensions missing | Verify volume at `CODER_HOME` | +| Claude not found | Run `which claude` to verify PATH | + +--- + +## Credits + +- [code-server](https://github.com/coder/code-server) by Coder +- [Claude Code](https://github.com/anthropics/claude-code) by Anthropic + +**License:** MIT diff --git a/railway-entrypoint.sh b/railway-entrypoint.sh index c63536dbb..705c0f17b 100644 --- a/railway-entrypoint.sh +++ b/railway-entrypoint.sh @@ -1,68 +1,185 @@ #!/bin/bash set -e -# Railway runs containers as root, but we want all data in /home/coder (the volume) -# This script ensures proper environment setup for persistence +# ============================================================================ +# VSCode Cloud IDE - Railway Entrypoint +# Robust entrypoint with volume-first tool priority +# ============================================================================ -echo "=== Railway Code-Server Startup ===" -echo "User: $(whoami) (UID: $(id -u))" -echo "HOME: $HOME" -echo "XDG_DATA_HOME: $XDG_DATA_HOME" -echo "XDG_CONFIG_HOME: $XDG_CONFIG_HOME" +echo "╔══════════════════════════════════════════════════════════════════════╗" +echo "║ VSCode Cloud IDE - Claude Code & Node.js Ready ║" +echo "╚══════════════════════════════════════════════════════════════════════╝" +echo "" -# Ensure HOME is set correctly (Railway might override it) -export HOME=/home/coder +# ============================================================================ +# CONFIGURABLE PATHS +# Users can override CODER_HOME to change the volume mount point +# ============================================================================ -# Ensure XDG directories exist and are writable -mkdir -p "$XDG_DATA_HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" "$XDG_STATE_HOME" -mkdir -p "$HOME/.local/bin" "$HOME/entrypoint.d" +CODER_HOME="${CODER_HOME:-/home/coder}" -# Ensure the code-server data directory exists -mkdir -p "$XDG_DATA_HOME/code-server" +export HOME="$CODER_HOME" +export XDG_DATA_HOME="$CODER_HOME/.local/share" +export XDG_CONFIG_HOME="$CODER_HOME/.config" +export XDG_CACHE_HOME="$CODER_HOME/.cache" +export XDG_STATE_HOME="$CODER_HOME/.local/state" + +# PATH: Volume paths FIRST (user installs), image paths LAST (fallbacks) +export PATH="$CODER_HOME/.local/node/bin:$CODER_HOME/.claude/local:$CODER_HOME/.local/bin:$CODER_HOME/node_modules/.bin:/usr/local/bin:/usr/bin:/usr/lib/code-server/lib/vscode/bin/remote-cli:$PATH" + +echo "→ User: $(whoami) (UID: $(id -u))" +echo "→ HOME: $HOME" + +# ============================================================================ +# DIRECTORY CREATION +# ============================================================================ + +create_dirs() { + mkdir -p "$XDG_DATA_HOME" \ + "$XDG_CONFIG_HOME" \ + "$XDG_CACHE_HOME" \ + "$XDG_STATE_HOME" \ + "$HOME/.local/bin" \ + "$HOME/.local/node" \ + "$HOME/.claude" \ + "$HOME/entrypoint.d" \ + "$HOME/workspace" \ + "$XDG_DATA_HOME/code-server/extensions" \ + "$XDG_CONFIG_HOME/code-server" 2>/dev/null || true +} +create_dirs + +# ============================================================================ +# ROOT USER SYMLINKS +# When running as root, symlink /root directories to the volume +# ============================================================================ -# Set up VS Code Server data directory symlink if running as root -# This ensures extensions installed by code-server persist if [ "$(id -u)" = "0" ]; then - # Create root's directories pointing to volume - mkdir -p /root/.local + echo "→ Running as root - creating persistence symlinks..." - # Symlink root's local share to the volume (for VS Code extensions) - if [ ! -L /root/.local/share ]; then - rm -rf /root/.local/share 2>/dev/null || true - ln -sf "$XDG_DATA_HOME" /root/.local/share - fi + mkdir -p /root/.local 2>/dev/null || true - # Symlink root's config to the volume - if [ ! -L /root/.config ]; then - rm -rf /root/.config 2>/dev/null || true - ln -sf "$XDG_CONFIG_HOME" /root/.config - fi + for dir in ".local/share" ".local/node" ".config" ".cache" ".claude"; do + target="$CODER_HOME/$dir" + link="/root/$dir" - # Symlink root's cache to the volume - if [ ! -L /root/.cache ]; then - rm -rf /root/.cache 2>/dev/null || true - ln -sf "$XDG_CACHE_HOME" /root/.cache - fi + if [ -d "$target" ] && [ ! -L "$link" ]; then + rm -rf "$link" 2>/dev/null || true + mkdir -p "$(dirname "$link")" 2>/dev/null || true + ln -sf "$target" "$link" 2>/dev/null || true + fi + done - echo "Created symlinks from /root to $HOME for persistence" + echo " ✓ Root directories symlinked to $CODER_HOME" fi -# Ensure Node.js is accessible -echo "Node.js version: $(node --version 2>/dev/null || echo 'Not found in PATH')" -echo "npm version: $(npm --version 2>/dev/null || echo 'Not found in PATH')" -echo "PATH: $PATH" +# ============================================================================ +# FIRST RUN SETUP +# ============================================================================ + +FIRST_RUN_MARKER="$XDG_DATA_HOME/.vscode-cloud-initialized" + +if [ ! -f "$FIRST_RUN_MARKER" ]; then + echo "→ First run detected - initializing..." + + mkdir -p "$XDG_CONFIG_HOME/code-server" 2>/dev/null || true + + if [ ! -f "$HOME/workspace/README.md" ]; then + cat > "$HOME/workspace/README.md" << 'WELCOME' 2>/dev/null || true +# Welcome to VSCode Cloud IDE + +Your cloud development environment is ready! + +## Features + +- **Claude Code CLI** - Pre-installed and ready to use +- **Node.js 20 LTS** - Pre-installed and ready to use +- **Persistent Extensions** - Install once, keep forever +- **Full Terminal** - npm, git, and more + +## Quick Start + +1. Open the Extensions panel (Ctrl+Shift+X) +2. Install your favorite extensions (they persist!) +3. Start coding in this workspace + +## Using Claude Code + +```bash +claude +``` + +You'll need to authenticate with your Anthropic API key on first use. + +Happy coding! 🚀 +WELCOME + fi + + touch "$FIRST_RUN_MARKER" 2>/dev/null || true + echo " ✓ Initialization complete" +fi + +# ============================================================================ +# ENVIRONMENT VERIFICATION +# Shows which version is being used (volume vs image) +# ============================================================================ + +echo "" +echo "Environment:" + +# Node.js - show source +if [ -x "$CODER_HOME/.local/node/bin/node" ]; then + echo " → Node.js: $(node --version 2>/dev/null) [volume]" +else + echo " → Node.js: $(node --version 2>/dev/null || echo 'not found') [image]" +fi + +# npm +echo " → npm: $(npm --version 2>/dev/null || echo 'not found')" + +# git +echo " → git: $(git --version 2>/dev/null | cut -d' ' -f3 || echo 'not found')" + +# Claude Code - show source +if [ -x "$CODER_HOME/.claude/local/claude" ]; then + echo " → claude: $(claude --version 2>/dev/null || echo 'installed') [volume]" +elif command -v claude &>/dev/null; then + echo " → claude: $(claude --version 2>/dev/null || echo 'installed') [image]" +else + echo " → claude: not installed" +fi + +# Extensions count +if [ -d "$XDG_DATA_HOME/code-server/extensions" ]; then + EXT_COUNT=$(find "$XDG_DATA_HOME/code-server/extensions" -maxdepth 1 -type d 2>/dev/null | wc -l) + EXT_COUNT=$((EXT_COUNT - 1)) + if [ $EXT_COUNT -gt 0 ]; then + echo " → Extensions: $EXT_COUNT installed" + fi +fi + +# ============================================================================ +# CUSTOM STARTUP SCRIPTS +# ============================================================================ -# Run any custom startup scripts from entrypoint.d if [ -d "$HOME/entrypoint.d" ]; then for script in "$HOME/entrypoint.d"/*.sh; do if [ -f "$script" ] && [ -x "$script" ]; then - echo "Running startup script: $script" - "$script" + echo "" + echo "Running: $(basename "$script")" + "$script" || echo " ⚠ Script exited with code $?" fi done fi -echo "=== Starting code-server ===" +# ============================================================================ +# START CODE-SERVER +# ============================================================================ + +echo "" +echo "════════════════════════════════════════════════════════════════════════" +echo "Starting code-server..." +echo "════════════════════════════════════════════════════════════════════════" +echo "" -# Start code-server with dumb-init for proper signal handling exec dumb-init /usr/bin/code-server "$@" diff --git a/railway.toml b/railway.toml new file mode 100644 index 000000000..e12903c58 --- /dev/null +++ b/railway.toml @@ -0,0 +1,13 @@ +# ============================================================================ +# VSCode Cloud IDE - Railway Configuration +# ============================================================================ + +[build] +builder = "DOCKERFILE" +dockerfilePath = "Dockerfile" + +[deploy] +healthcheckPath = "/healthz" +healthcheckTimeout = 300 +restartPolicyType = "ON_FAILURE" +restartPolicyMaxRetries = 10